from api.service.translation_service import translation_service
import os
import imaplib
import smtplib
import email
import ssl
import re
import html
import uuid
import socket
import base64
import hashlib
from typing import Optional
from email.header import decode_header
from sqlalchemy import func
from sqlalchemy.orm import Session
from bs4 import BeautifulSoup
from fastapi import HTTPException
from fastapi import status as http_status
from api.database import SessionLocal
from api.model.email_account import EmailAccount
from api.model.email import Email
from api.model.email_folder import EmailFolder
from api.utils.helpers import (
    decode_modified_utf7,
    encode_modified_utf7,
    generate_content_hash,
    generate_imap_key,
    ensure_message_id
)
from api.utils.cache import cache, failed_email_tracker
from api.utils.logger import get_logger
from api.utils.ssl_helper import create_imap_connection, create_smtp_connection
from api.utils.proxy_helper import create_imap_connection_with_proxy, create_smtp_connection_with_proxy
from api.service.email_sync_helper import (
    get_uidvalidity,
    delete_folder_relations,
    find_or_create_email_by_message_id,
    create_or_update_folder_relation
)
from api.utils.db_retry import retry_on_db_lock
from api.utils.folder_helper import strip_namespace_folder, get_server_folder_path, add_namespace_folder

# 初始化日志
logger = get_logger("email_service")
sync_logger = get_logger("sync")

class EmailService:
    def __init__(self, account_id: int = None, account: EmailAccount = None):
        if account_id:
            self.account_id = account_id
            self.account = None
        elif account:
            self.account = account
            self.account_id = account.id
        self.imap = None
    
    @staticmethod
    def _update_sync_status_with_retry(account_id: int, status: str, max_retries: int = 10, base_delay: float = 1.0):
        """
        使用独立数据库会话和重试机制更新同步状态
        
        Args:
            account_id: 账户ID
            status: 新状态
            max_retries: 最大重试次数
            base_delay: 基础延迟时间（秒）
        """
        import time
        from sqlalchemy.exc import OperationalError
        
        last_error = None
        for attempt in range(max_retries):
            db = None
            try:
                db = SessionLocal()
                account = db.query(EmailAccount).filter(EmailAccount.id == account_id).first()
                if account:
                    account.sync_status = status
                    db.commit()
                    return True
                return False
            except OperationalError as e:
                if db:
                    db.rollback()
                error_msg = str(e).lower()
                if 'database is locked' in error_msg or 'locked' in error_msg:
                    last_error = e
                    if attempt < max_retries - 1:
                        # 使用指数退避策略
                        delay = base_delay * (2 ** attempt)
                        time.sleep(delay)
                        continue
                raise
            except Exception as e:
                if db:
                    db.rollback()
                raise
            finally:
                if db:
                    db.close()
        
        # 所有重试都失败
        if last_error:
            logger.error(f"更新同步状态失败，已重试{max_retries}次: {last_error}")
            raise last_error
    
    def sync_emails(self, sync_start_time: str = None):
        """同步邮件

        Args:
            sync_start_time: 本次同步的开始时间标识（时:分:秒），用于日志中区分不同次同步
        """
        db: Session = SessionLocal()
        db.autoflush = False
        total_new_emails = 0
        all_new_email_objects = []
        # 保存同步开始时间到实例变量，供日志使用
        self.sync_start_time = sync_start_time
        # 记录上次NOOP时间，用于保持连接活跃
        import time
        self.last_noop_time = time.time()

        try:
            account = db.query(EmailAccount).filter(EmailAccount.id == self.account_id).first()
            if not account:
                logger.error(f"同步错误：未找到账户 ID {self.account_id}")
                return False, []

            self.account = account
            self.account.sync_status = "syncing"

            self._connect()

            folders = self._get_folders_to_sync()
            time_label = f"({self.sync_start_time})" if self.sync_start_time else ""
            sync_logger.info(f"【{self.account.email}】{time_label} 开始同步，共 {len(folders)} 个文件夹")
            
            # 【新增】检测并清理已删除的文件夹
            self._cleanup_deleted_folders(folders, db)
            
            for idx, folder in enumerate(folders):
                # 【优化】只在第一个文件夹和每隔3个文件夹时进行保活检查
                # 这样可以减少不必要的NOOP操作，同时保持连接活跃
                if idx == 0 or idx % 3 == 0:
                    self._smart_keep_alive(force=False)  # 使用智能判断而非强制

                new_emails_count, new_email_objects = self._sync_folder(folder, db)
                if isinstance(new_emails_count, int):
                    total_new_emails += new_emails_count
                    all_new_email_objects.extend(new_email_objects)
            from api.utils.helpers import get_beijing_time
            self.account.last_sync = get_beijing_time()
            self.account.sync_status = "idle"
            
            # 使用重试机制提交数据库更改
            @retry_on_db_lock(max_retries=5, delay=1, backoff=1.5)
            def commit_sync_status():
                db.commit()
            
            commit_sync_status()
            
            # 在Session关闭前,预先加载所有Email对象的属性到内存中(避免DetachedInstanceError)
            for email_obj in all_new_email_objects:
                # 【v2.0】访问所有需要的属性,强制加载到内存
                _ = email_obj.id
                _ = email_obj.email_account_id
                _ = email_obj.message_id
                _ = email_obj.subject
                _ = email_obj.sender
                _ = email_obj.recipients
                _ = email_obj.cc
                _ = email_obj.bcc
                _ = email_obj.summary
                _ = email_obj.is_read
                _ = email_obj.has_attachments
                _ = email_obj.size
                _ = email_obj.received_date
                _ = email_obj.sent_date
                _ = email_obj.is_deleted
                _ = email_obj.created_at
                _ = email_obj.updated_at
            
            time_label = f"({self.sync_start_time})" if self.sync_start_time else ""
            return total_new_emails, all_new_email_objects
        except Exception as e:
            # 不输出详细堆栈，只记录简要错误信息
            sync_logger.warning(f"【{self.account.email}】邮件同步失败: {type(e).__name__}")
            db.rollback()
            
            # 更新失败状态（使用重试机制和独立的数据库会话）
            try:
                self._update_sync_status_with_retry(self.account_id, "failed")
            except Exception as inner_e:
                logger.error(f"设置同步状态为'失败'时出错: {inner_e}")
            
            return False, []
        finally:
            self._disconnect()
            db.close()

    def delete_folder(self, folder_name: str):
        """
        在IMAP服务器上删除指定的文件夹。
        如果文件夹不为空，则先将其中的所有邮件移动到收件箱。
        """
        db: Session = SessionLocal()
        try:
            self._connect()
            # 获取服务器上的原始文件夹名称（会自动处理命名空间）
            original_folder_name = self._get_original_folder_name(folder_name)
            
            # 选择要删除的文件夹
            status, _ = self.imap.select(f'"{original_folder_name}"', readonly=False)
            
            # 如果文件夹存在或可选，则处理其中的邮件
            if status == 'OK':
                # 检查文件夹中是否有邮件
                status, uids_data = self.imap.uid('search', None, "ALL")
                if status == 'OK' and uids_data[0]:
                    uids = uids_data[0].split()
                    if uids:
                        # 将UID列表转换为逗号分隔的字符串
                        uid_set_str = b','.join(uids)
                        decoded_uids = [uid.decode() for uid in uids]
                        target_folder = "INBOX"
                        encoded_target_folder = encode_modified_utf7(target_folder)

                        # 1. 复制所有邮件到收件箱
                        copy_status, _ = self.imap.uid('COPY', uid_set_str, f'"{encoded_target_folder}"')
                        if copy_status != 'OK':
                            raise Exception(f"无法复制邮件到 '{target_folder}'")
                        
                        logger.info(f"文件夹 '{folder_name}': 已复制 {len(uids)} 封邮件到 '{target_folder}'")

                        # 2. 在原文件夹中标记所有邮件为删除
                        store_status, _ = self.imap.uid('STORE', uid_set_str, '+FLAGS', '(\\Deleted)')
                        if store_status != 'OK':
                            logger.error(f"警告: 无法在 '{folder_name}' 中标记邮件为已删除")

                        # 3. 执行 expunge
                        self.imap.expunge()

                        # 4. 更新数据库（v2.0：更新EmailFolder关联表）
                        # 删除源文件夹的关联，创建目标文件夹的关联
                        for uid_bytes in uids:
                            uid_str = uid_bytes.decode()
                            # 查找源文件夹关联
                            source_rel = db.query(EmailFolder).filter(
                                EmailFolder.email_account_id == self.account.id,
                                EmailFolder.folder_name == folder_name,
                                EmailFolder.uid == uid_str
                            ).first()
                            
                            if source_rel:
                                email_id = source_rel.email_id
                                # 删除源关联
                                db.delete(source_rel)
                                
                                # 创建目标关联（UID在新文件夹中可能不同，需要重新获取）
                                # 这里暂时使用相同的UID，实际应该从IMAP查询
                                target_rel = EmailFolder(
                                    email_id=email_id,
                                    email_account_id=self.account.id,
                                    folder_name=target_folder,
                                    uid=uid_str,  # 注意：实际UID可能不同
                                    uidvalidity=source_rel.uidvalidity
                                )
                                db.add(target_rel)
                        
                        db.commit()
                        logger.info(f"已更新数据库中 {len(uids)} 条邮件关联到文件夹 '{target_folder}'")

                # 在删除前必须先关闭文件夹
                self.imap.close()
            
            # 执行删除操作
            status, response = self.imap.delete(f'"{original_folder_name}"')
            
            if status != 'OK':
                raise Exception(f"无法在服务器上删除文件夹: {response}")

            logger.info(f"文件夹 '{folder_name}' 已成功从IMAP服务器删除。")

        except Exception as e:
            db.rollback()
            logger.error(f"删除文件夹时发生异常: {e}", exc_info=True)
            # 重新抛出异常，以便控制器可以捕获它并返回适当的错误响应
            raise
        finally:
            self._disconnect()
            db.close()

    def move_emails_to_trash(self, db: Session, email_ids: list[int]):
        """
        【v2.0改造】将邮件移动到回收站
        注意：此方法不会关闭传入的db会话，调用者负责管理会话生命周期
        """
        try:
            emails_to_move = db.query(Email).filter(Email.id.in_(email_ids)).all()
            if not emails_to_move:
                raise HTTPException(status_code=404, detail="未找到要移动到回收站的邮件")

            self._connect()
            
            target_folder = "Trash"
            encoded_target_folder = encode_modified_utf7(target_folder)

            # 【v2.0】按EmailFolder关联分组处理
            from collections import defaultdict
            relations_by_folder = defaultdict(list)
            
            for email_record in emails_to_move:
                # 获取该邮件的所有文件夹关联
                relations = db.query(EmailFolder).filter(
                    EmailFolder.email_id == email_record.id,
                    EmailFolder.email_account_id == self.account.id
                ).all()
                
                for rel in relations:
                    relations_by_folder[rel.folder_name].append((email_record, rel))
            
            # 对每个源文件夹分别处理
            for current_folder, items in relations_by_folder.items():
                # 获取服务器上的原始文件夹名称
                original_current_folder = self._get_original_folder_name(current_folder)
                
                status, _ = self.imap.select(f'"{original_current_folder}"', readonly=False)
                if status != 'OK':
                    logger.error(f"无法在服务器上选择文件夹 '{current_folder}'，跳过这些邮件")
                    continue

                for email_record, relation in items:
                    # 1. 复制到回收站
                    uid_str = str(relation.uid)
                    copy_result, _ = self.imap.uid('COPY', uid_str, f'"{encoded_target_folder}"')
                    if copy_result != 'OK':
                        logger.error(f"警告: 无法将邮件 {email_record.id} (UID: {uid_str}) 复制到回收站")
                        continue

                    # 2. 标记删除
                    store_result, _ = self.imap.uid('STORE', uid_str, '+FLAGS', '(\\Deleted)')
                    if store_result != 'OK':
                        logger.error(f"警告: 无法在 '{current_folder}' 中标记邮件 {email_record.id} 为已删除")
                    
                    # 3. 删除旧的文件夹关联
                    db.delete(relation)
                
                # 4. 清理当前文件夹
                self.imap.expunge()
            
            # 5. 同步Trash文件夹以创建新的关联
            try:
                # 获取Trash文件夹信息
                original_trash_folder = self._get_original_folder_name(target_folder)
                status, select_data = self.imap.select(f'"{original_trash_folder}"', readonly=True)
                
                if status == 'OK':
                    # 获取UIDVALIDITY
                    from api.service.email_sync_helper import get_uidvalidity
                    trash_uidvalidity = get_uidvalidity(
                        self.imap, select_data, original_trash_folder, target_folder, self.account.id
                    )
                    
                    # 为每封已移动的邮件在Trash文件夹中查找新UID并创建关联
                    for email_record in emails_to_move:
                        try:
                            # 通过Message-ID搜索邮件
                            message_id = email_record.message_id
                            if message_id:
                                clean_message_id = message_id.strip('<>')
                                search_query = f'HEADER Message-ID "{clean_message_id}"'
                                status, data = self.imap.uid('search', None, search_query)
                                
                                if status == 'OK' and data[0]:
                                    uids = data[0].split()
                                    if uids:
                                        # 使用最后一个UID（最新的）
                                        new_uid = uids[-1].decode()
                                        
                                        # 创建Trash文件夹关联
                                        from api.service.email_sync_helper import create_or_update_folder_relation
                                        create_or_update_folder_relation(
                                            email_id=email_record.id,
                                            email_account_id=self.account.id,
                                            folder_name=target_folder,
                                            uid=new_uid,
                                            uidvalidity=trash_uidvalidity,
                                            flags='',
                                            db=db
                                        )
                                        logger.info(f"已为邮件 {email_record.id} 创建Trash文件夹关联 (UID: {new_uid})")
                        except Exception as e:
                            logger.error(f"为邮件 {email_record.id} 创建Trash关联失败: {e}")
                            
            except Exception as e:
                logger.error(f"同步Trash文件夹失败: {e}")

            db.commit()
            logger.info(f"已将 {len(emails_to_move)} 封邮件移至回收站并完成同步")

        except HTTPException:
            db.rollback()
            raise
        except Exception as e:
            db.rollback()
            logger.error(f"移动邮件到回收站失败: {e}", exc_info=True)
            raise HTTPException(status_code=500, detail=str(e))
        finally:
            self._disconnect()

    def _get_original_folder_name(self, decoded_folder_name: str) -> str:
        """
        获取文件夹在服务器上的原始名称（优先从缓存获取）
        会自动处理命名空间文件夹的添加
        """
        # 尝试从缓存获取文件夹映射
        cache_key = f"account_folders:{self.account.id}"
        cached_data = cache.get(cache_key)
        
        if cached_data is not None:
            folder_mapping = cached_data.get("folder_mapping", {})
            if decoded_folder_name in folder_mapping:
                encoded_name = folder_mapping[decoded_folder_name].get("encoded_name")
                if encoded_name:
                    return encoded_name
        
        # 缓存未命中，从IMAP服务器获取
        try:
            # 使用标准IMAP LIST参数以兼容更多服务器
            try:
                status, folder_list = self.imap.list('""', '*')
            except:
                status, folder_list = self.imap.list()
            if status == 'OK':
                # 收集所有服务器文件夹用于命名空间处理
                all_server_folders = []
                folder_map = {}
                
                for folder_data in folder_list:
                    line_str = folder_data.decode('utf-8', 'ignore')
                    # 支持两种格式：name有引号和无引号
                    match = re.match(r'\((.*?)\)\s+"(.*?)"\s+"?(.*?)"?$', line_str)
                    if match:
                        flags, delimiter, original_name = match.groups()
                        original_name = original_name.strip('"')  # 移除可能的引号
                        decoded_name = decode_modified_utf7(original_name)
                        all_server_folders.append(decoded_name)
                        
                        # 剥离命名空间后建立映射
                        stripped_name = strip_namespace_folder(decoded_name, self.account.email)
                        folder_map[stripped_name] = original_name
                        
                        # 检查是否是特殊文件夹
                        if r'\Sent' in flags:
                            folder_map["Sent"] = original_name
                        elif r'\Junk' in flags:
                            folder_map["Junk"] = original_name
                        elif r'\Trash' in flags:
                            folder_map["Trash"] = original_name
                        # 基于文件夹名称的反向映射
                        elif decoded_name in ["Sent Messages", "Sent Items", "已发送邮件", "已发送"]:
                            folder_map["Sent"] = original_name
                        elif decoded_name in ["Deleted Messages", "Deleted Items", "已删除邮件", "已删除"]:
                            folder_map["Trash"] = original_name
                
                # 在映射中查找
                if decoded_folder_name in folder_map:
                    return folder_map[decoded_folder_name]
                
                # 如果没找到，尝试添加命名空间
                name_with_ns = add_namespace_folder(decoded_folder_name, self.account.email, all_server_folders)
                if name_with_ns in folder_map:
                    return folder_map[name_with_ns]
                
        except Exception as e:
            logger.error(f"获取原始文件夹名称时出错: {e}")
        
        # 如果找不到，使用encode_modified_utf7编码后返回
        encoded_name = encode_modified_utf7(decoded_folder_name)
        return encoded_name

    def _save_eml_file(self, email_id: int, raw_email_data: bytes):
        """
        保存原始邮件数据为EML文件（不包含附件内容，仅包含附件信息）
        
        Args:
            email_id: 邮件ID
            raw_email_data: 原始邮件字节数据
            
        Returns:
            保存的EML文件路径
        """
        try:
            # 解析邮件
            original_msg = email.message_from_bytes(raw_email_data)
            
            # 创建新的邮件对象，只复制必要的头部和正文（排除附件内容）
            from email.mime.multipart import MIMEMultipart
            from email.mime.text import MIMEText
            from email.mime.image import MIMEImage
            
            cached_msg = MIMEMultipart('related')  # 使用 related 支持内嵌图片
            
            # 只复制必要的邮件头（排除 Content-Type 和 Content-Transfer-Encoding）
            skip_headers = ['content-type', 'content-transfer-encoding', 'mime-version']
            for header, value in original_msg.items():
                if header.lower() not in skip_headers:
                    cached_msg[header] = value
            
            # 遍历原始邮件，只保留正文和内嵌图片
            if original_msg.is_multipart():
                for part in original_msg.walk():
                    content_type = part.get_content_type()
                    content_disposition = str(part.get("Content-Disposition"))
                    content_id = part.get("Content-ID")
                    
                    # 跳过multipart容器本身
                    if part.get_content_maintype() == 'multipart':
                        continue
                    
                    # 跳过附件（非内嵌图片的附件）
                    if "attachment" in content_disposition and not content_id:
                        continue
                    
                    # 保留正文和内嵌图片，但需要重新编码
                    if content_type in ["text/plain", "text/html"]:
                        # 解码内容（自动处理 quoted-printable、base64 等编码）
                        try:
                            payload = part.get_payload(decode=True)
                            charset = part.get_content_charset() or 'utf-8'
                            decoded_text = payload.decode(charset, errors='ignore')
                            
                            # 创建新的文本部分
                            new_part = MIMEText(decoded_text, _subtype=content_type.split('/')[-1], _charset='utf-8')
                            # 复制重要的头部信息
                            for header in ['Content-ID', 'Content-Location']:
                                if part.get(header):
                                    new_part[header] = part.get(header)
                            cached_msg.attach(new_part)
                        except Exception as e:
                            logger.error(f"解码邮件内容失败: {e}，保留原始部分")
                            cached_msg.attach(part)
                    elif content_id:
                        # 内嵌图片，解码并重新编码
                        try:
                            image_data = part.get_payload(decode=True)
                            new_part = MIMEImage(image_data, _subtype=content_type.split('/')[-1])
                            # 复制重要的头部信息
                            if content_id:
                                new_part['Content-ID'] = content_id
                            if part.get('Content-Location'):
                                new_part['Content-Location'] = part.get('Content-Location')
                            cached_msg.attach(new_part)
                        except Exception as e:
                            logger.error(f"处理内嵌图片失败: {e}，保留原始部分")
                            cached_msg.attach(part)
            else:
                # 单部分邮件，解码并重新编码
                try:
                    payload = original_msg.get_payload(decode=True)
                    charset = original_msg.get_content_charset() or 'utf-8'
                    decoded_text = payload.decode(charset, errors='ignore')
                    cached_msg.set_payload(decoded_text, charset='utf-8')
                except Exception as e:
                    logger.error(f"解码单部分邮件失败: {e}，使用原始内容")
                    cached_msg.set_payload(original_msg.get_payload())
                    cached_msg.set_charset(original_msg.get_content_charset())
            
            # 保存到文件
            filename = f"{email_id}.eml"
            dir_path = os.path.join("emails", self.account.email)
            os.makedirs(dir_path, exist_ok=True)
            file_path = os.path.join(dir_path, filename)
            
            with open(file_path, "wb") as f:
                f.write(cached_msg.as_bytes())
            
            return file_path
            
        except Exception as e:
            logger.error(f"保存EML文件失败: {e}", exc_info=True)
            return None

    def fetch_and_cache_email_without_attachments(self, db: Session, email_record: Email):
        """
        【v2.0改造】获取邮件并缓存为.eml文件（排除附件内容）
        只保存邮件头、正文和内嵌图片，不保存附件
        返回：缓存的.eml文件路径
        """
        # 检查缓存是否已存在
        if email_record.eml_path and os.path.exists(email_record.eml_path):
            return email_record.eml_path
        
        logger.info(f"缓存邮件 {email_record.id}（不含附件）")
        
        try:
            self._connect()

            # 【v2.0】从EmailFolder获取任意一个文件夹关联
            relation = db.query(EmailFolder).filter(
                EmailFolder.email_id == email_record.id,
                EmailFolder.email_account_id == self.account.id
            ).first()
            
            if not relation:
                raise ConnectionError(f"邮件 {email_record.id} 没有文件夹关联")

            original_folder_name = self._get_original_folder_name(relation.folder_name)

            # 使用重试机制选择文件夹
            select_status, select_data = self._imap_operation_with_retry(
                lambda: self.imap.select(f'"{original_folder_name}"', readonly=True),
                operation_name=f"选择文件夹 {relation.folder_name}"
            )
            if select_status != 'OK':
                raise ConnectionError(f"无法选择文件夹")

            uid_str = str(relation.uid)

            # 使用重试机制获取完整邮件
            fetch_status, msg_data = self._imap_operation_with_retry(
                lambda: self.imap.uid('fetch', uid_str, '(BODY.PEEK[])'),
                operation_name=f"获取邮件内容 UID={uid_str}"
            )
            if fetch_status != 'OK' or not msg_data or msg_data[0] is None:
                raise ConnectionError(f"无法获取邮件内容")
            
            if not isinstance(msg_data[0], tuple) or len(msg_data[0]) < 2:
                raise ConnectionError(f"邮件数据结构异常")
            
            raw_email = msg_data[0][1]
            if not raw_email:
                raise ConnectionError(f"邮件数据为空")
            
            # 使用新的_save_eml_file方法保存
            file_path = self._save_eml_file(email_record.id, raw_email)
            if not file_path:
                raise Exception("保存EML文件失败")
            
            # 更新数据库
            if email_record.eml_path != file_path:
                email_record.eml_path = file_path
                db.commit()
            
            logger.info(f"邮件 {email_record.id} 已缓存到 {file_path}（不含附件）")
            return file_path
            
        except Exception as e:
            logger.error(f"缓存邮件失败: {e}", exc_info=True)
            raise HTTPException(
                status_code=http_status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail=f"缓存邮件失败: {e}"
            )
        finally:
            self._disconnect()
    
    def fetch_email_content_on_demand(self, db: Session, email_record: Email):
        """
        按需获取邮件内容（只获取正文，不包括附件）
        优先从缓存读取，如果没有缓存则从IMAP获取
        返回：(text_body, html_body, inline_images_dict)
        """
        # 1. 尝试从缓存读取
        if email_record.eml_path and os.path.exists(email_record.eml_path):
            try:
                with open(email_record.eml_path, 'rb') as f:
                    raw_email = f.read()
                msg = email.message_from_bytes(raw_email)
                return self._parse_email_content(msg)
            except Exception as e:
                logger.error(f"从缓存读取失败，将从IMAP获取: {e}")
        
        # 2. 缓存不存在，先缓存邮件（不含附件）
        try:
            cache_path = self.fetch_and_cache_email_without_attachments(db, email_record)
            
            # 3. 从缓存读取内容
            with open(cache_path, 'rb') as f:
                raw_email = f.read()
            msg = email.message_from_bytes(raw_email)
            return self._parse_email_content(msg)
            
        except Exception as e:
            logger.error(f"获取邮件内容失败: {e}", exc_info=True)
            raise HTTPException(
                status_code=http_status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail=f"获取邮件内容失败: {e}"
            )
    
    def _parse_email_content(self, msg: email.message.Message):
        """
        解析邮件内容（从email.message对象）
        返回：(text_body, html_body, inline_images_dict)
        """
        text_body = ""
        html_body = ""
        inline_images = {}
        
        if msg.is_multipart():
            for part in msg.walk():
                content_type = part.get_content_type()
                content_disposition = str(part.get("Content-Disposition"))
                content_id = part.get("Content-ID")
                
                # 跳过附件
                if "attachment" in content_disposition:
                    continue
                
                # 提取正文
                if content_type == "text/html" and not html_body:
                    try:
                        payload = part.get_payload(decode=True)
                        charset = part.get_content_charset() or 'utf-8'
                        html_body = payload.decode(charset, errors='ignore')
                    except Exception as e:
                        logger.error(f"解码HTML正文失败: {e}")
                
                elif content_type == "text/plain" and not text_body:
                    try:
                        payload = part.get_payload(decode=True)
                        charset = part.get_content_charset() or 'utf-8'
                        text_body = payload.decode(charset, errors='ignore')
                    except Exception as e:
                        logger.error(f"解码文本正文失败: {e}")
                
                # 提取内嵌图片
                if content_id and "attachment" not in content_disposition:
                    cid = content_id.strip("<>")
                    try:
                        image_data = part.get_payload(decode=True)
                        inline_images[cid] = {
                            "data": image_data,
                            "type": part.get_content_type()
                        }
                    except Exception as e:
                        logger.error(f"提取内嵌图片失败: {e}")
        else:
            content_type = msg.get_content_type()
            try:
                payload = msg.get_payload(decode=True)
                charset = msg.get_content_charset() or 'utf-8'
                if content_type == "text/html":
                    html_body = payload.decode(charset, errors='ignore')
                else:
                    text_body = payload.decode(charset, errors='ignore')
            except Exception as e:
                logger.error(f"解码邮件正文失败: {e}")
        
        return text_body, html_body, inline_images
    
    def fetch_email_attachments_info(self, db: Session, email_record: Email):
        """
        【v2.0改造】获取邮件的附件信息列表（不下载附件内容）
        优先从本地EML文件读取，如果没有则从IMAP服务器获取
        返回：[{index, filename, size, content_type}, ...]
        """
        
        try:
            # 1. 尝试从本地EML文件读取
            if email_record.eml_path and os.path.exists(email_record.eml_path):
                logger.info(f"从本地EML文件读取附件信息: {email_record.eml_path}")
                with open(email_record.eml_path, 'rb') as f:
                    raw_email = f.read()
                msg = email.message_from_bytes(raw_email)
            else:
                # 2. 如果本地没有，则从IMAP服务器获取
                logger.info(f"本地EML文件不存在，从IMAP服务器获取附件信息")
                self._connect()
                
                # 【v2.0】从EmailFolder获取任意一个文件夹关联
                relation = db.query(EmailFolder).filter(
                    EmailFolder.email_id == email_record.id,
                    EmailFolder.email_account_id == self.account.id
                ).first()
                
                if not relation:
                    raise ConnectionError(f"邮件 {email_record.id} 没有文件夹关联")
                
                original_folder_name = self._get_original_folder_name(relation.folder_name)
                select_status, select_data = self.imap.select(f'"{original_folder_name}"', readonly=True)
                if select_status != 'OK':
                    raise ConnectionError(f"无法选择文件夹")
                
                uid_str = str(relation.uid)
                
                # 获取完整邮件
                fetch_status, msg_data = self.imap.uid('fetch', uid_str, '(BODY.PEEK[])')
                
                if fetch_status != 'OK' or not msg_data or msg_data[0] is None:
                    raise ConnectionError(f"无法获取邮件数据")
                
                if not isinstance(msg_data[0], tuple) or len(msg_data[0]) < 2:
                    raise ConnectionError(f"邮件数据结构异常")
                
                raw_email = msg_data[0][1]
                msg = email.message_from_bytes(raw_email)
                
                self._disconnect()
            
            attachments = []
            attachment_index = 0
            
            if msg.is_multipart():
                for part in msg.walk():
                    content_type = part.get_content_type()
                    content_disposition = str(part.get("Content-Disposition"))
                    content_id = part.get("Content-ID")
                    
                    # 识别附件
                    is_attachment = False
                    if "attachment" in content_disposition:
                        is_attachment = True
                    elif content_type not in ["text/plain", "text/html", "multipart/mixed", 
                                             "multipart/alternative", "multipart/related"] and not content_id:
                        is_attachment = True
                    
                    if is_attachment:
                        filename = part.get_filename()
                        if filename:
                            # 解码文件名
                            from email.header import decode_header
                            decoded_filename = decode_header(filename)[0]
                            if isinstance(decoded_filename[0], bytes):
                                filename = decoded_filename[0].decode(decoded_filename[1] or 'utf-8', errors='ignore')
                            else:
                                filename = decoded_filename[0]
                            
                            # 获取附件大小（通过获取payload）
                            payload = part.get_payload(decode=True)
                            size = len(payload) if payload else 0
                            
                            attachments.append({
                                "index": attachment_index,
                                "filename": filename,
                                "size": size,
                                "content_type": content_type
                            })
                            attachment_index += 1
            
            return attachments
            
        except Exception as e:
            logger.error(f"获取附件信息失败: {e}", exc_info=True)
            raise HTTPException(
                status_code=http_status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail=f"获取附件信息失败: {e}"
            )
        finally:
            self._disconnect()
    
    def fetch_single_attachment(self, db: Session, email_record: Email, attachment_index: int):
        """
        【v2.0改造】按需获取单个附件
        优先从本地EML文件读取，如果没有则从IMAP服务器获取
        返回：(filename, content_type, data)
        """
        
        try:
            # 1. 尝试从本地EML文件读取
            if email_record.eml_path and os.path.exists(email_record.eml_path):
                logger.info(f"从本地EML文件读取附件: {email_record.eml_path}")
                with open(email_record.eml_path, 'rb') as f:
                    raw_email = f.read()
                msg = email.message_from_bytes(raw_email)
            else:
                # 2. 如果本地没有，则从IMAP服务器获取
                logger.info(f"本地EML文件不存在，从IMAP服务器获取附件")
                self._connect()
                
                # 【v2.0】从EmailFolder获取任意一个文件夹关联
                relation = db.query(EmailFolder).filter(
                    EmailFolder.email_id == email_record.id,
                    EmailFolder.email_account_id == self.account.id
                ).first()
                
                if not relation:
                    raise ConnectionError(f"邮件 {email_record.id} 没有文件夹关联")
                
                original_folder_name = self._get_original_folder_name(relation.folder_name)
                select_status, select_data = self.imap.select(f'"{original_folder_name}"', readonly=True)
                if select_status != 'OK':
                    raise ConnectionError(f"无法选择文件夹")
                
                uid_str = str(relation.uid)
                
                # 获取完整邮件用于解析附件
                fetch_status, msg_data = self.imap.uid('fetch', uid_str, '(BODY.PEEK[])')
                
                if fetch_status != 'OK' or not msg_data or msg_data[0] is None:
                    raise ConnectionError(f"无法获取邮件数据")
                
                if not isinstance(msg_data[0], tuple) or len(msg_data[0]) < 2:
                    raise ConnectionError(f"邮件数据结构异常")
                
                raw_email = msg_data[0][1]
                msg = email.message_from_bytes(raw_email)
                
                self._disconnect()
            
            # 查找指定索引的附件
            current_index = 0
            if msg.is_multipart():
                for part in msg.walk():
                    content_type = part.get_content_type()
                    content_disposition = str(part.get("Content-Disposition"))
                    content_id = part.get("Content-ID")
                    
                    # 识别附件
                    is_attachment = False
                    if "attachment" in content_disposition:
                        is_attachment = True
                    elif content_type not in ["text/plain", "text/html", "multipart/mixed", 
                                             "multipart/alternative", "multipart/related"] and not content_id:
                        is_attachment = True
                    
                    if is_attachment:
                        filename = part.get_filename()
                        if filename:
                            if current_index == attachment_index:
                                # 解码文件名
                                from email.header import decode_header
                                decoded_filename = decode_header(filename)[0]
                                if isinstance(decoded_filename[0], bytes):
                                    filename = decoded_filename[0].decode(decoded_filename[1] or 'utf-8', errors='ignore')
                                else:
                                    filename = decoded_filename[0]
                                
                                # 获取附件数据
                                payload = part.get_payload(decode=True)
                                
                                return filename, content_type, payload
                            
                            current_index += 1
            
            raise HTTPException(
                status_code=http_status.HTTP_404_NOT_FOUND,
                detail=f"未找到附件 #{attachment_index}"
            )
            
        except Exception as e:
            logger.error(f"下载附件失败: {e}", exc_info=True)
            raise HTTPException(
                status_code=http_status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail=f"下载附件失败: {e}"
            )
        finally:
            self._disconnect()

    def fetch_emails_from_folder(self, folder: str, skip: int, limit: int):
        emails = []
        total = 0
        try:
            self._connect()
            self.imap.select(f'"{folder}"', readonly=True)
            
            status, uids_data = self.imap.uid('search', None, "ALL")
            if status == 'OK':
                uids = uids_data[0].split()
                total = len(uids)
                
                uids.reverse()
                
                paginated_uids = uids[skip : skip + limit]
                
                for uid in paginated_uids:
                    status, msg_data = self.imap.uid('fetch', uid, '(FLAGS BODY[HEADER.FIELDS (SUBJECT FROM TO DATE MESSAGE-ID)])')
                    
                    if status == 'OK':
                        import re
                        from email.parser import HeaderParser
                        
                        flags_part = msg_data[0][0].decode('utf-8')
                        flags_match = re.search(r'FLAGS \((.*?)\)', flags_part)
                        flags = flags_match.group(1).split() if flags_match else []
                        is_read = '\\Seen' in flags
                        
                        parser = HeaderParser()
                        headers = parser.parsestr(msg_data[0][1].decode('utf-8'))
                        
                        emails.append({
                            "id": int(uid),
                            "uid": uid.decode(),
                            "subject": headers['Subject'],
                            "sender": headers['From'],
                            "recipients": headers['To'],
                            "received_date": headers['Date'],
                            "folder": folder,
                            "is_read": is_read,
                        })
            
            self._disconnect()
            
        except Exception as e:
            logger.error(f"从文件夹 {folder} 获取邮件失败: {e}")
            return [], 0
            
        return emails, total

    def _connect(self, max_retries=1, retry_delay=3):
        """
        连接到IMAP服务器，支持自动重试和代理

        Args:
            max_retries: 最大重试次数（默认1次，即总共尝试2次）
            retry_delay: 重试间隔秒数（默认3秒）
        """
        import time

        last_error = None
        last_error_type = None
        
        # 获取代理配置
        proxy_config = None
        proxy_id = None
        if self.account.proxy_id:
            from api.model.proxy import Proxy
            db = SessionLocal()
            try:
                proxy = db.query(Proxy).filter(Proxy.id == self.account.proxy_id).first()
                if proxy and proxy.is_active:
                    proxy_id = proxy.id
                    proxy_config = {
                        'type': proxy.type,
                        'host': proxy.host,
                        'port': proxy.port,
                        'username': proxy.username,
                        'password': proxy.password,
                        'v2ray_config': proxy.v2ray_config
                    }
            finally:
                db.close()

        for attempt in range(max_retries + 1):  # +1 因为要算上初次尝试
            try:
                if attempt > 0:
                    time.sleep(retry_delay)

                # 直接创建连接（支持代理）
                timeout = 120 if proxy_config else 90
                
                if proxy_config:
                    self.imap = create_imap_connection_with_proxy(
                        server=self.account.imap_server,
                        port=self.account.imap_port,
                        use_ssl=self.account.imap_ssl,
                        proxy_config=proxy_config,
                        timeout=timeout
                    )
                else:
                    self.imap = create_imap_connection(
                        server=self.account.imap_server,
                        port=self.account.imap_port,
                        use_ssl=self.account.imap_ssl,
                        timeout=timeout
                    )
                
                # 登录认证
                t_login_start = time.perf_counter()
                self.imap.login(self.account.email, self.account.password)
                t_login_end = time.perf_counter()
                time_label_login = f"({self.sync_start_time})" if hasattr(self, 'sync_start_time') and self.sync_start_time else ""
                sync_logger.info(f"【{self.account.email}】{time_label_login} IMAP登录耗时: {t_login_end - t_login_start:.4f}s")
                
                # 【RFC 2971】登录后发送IMAP ID信息
                try:
                    args = b'("name" "MailClient" "version" "1.0.0" "vendor" "EmailSystem" "support-email" "support@example.com")'
                    tag = self.imap._new_tag()
                    self.imap.send(tag + b' ID ' + args + b'\r\n')
                    response = self.imap._get_response()
                except Exception:
                    pass
                
                time_label = f"({self.sync_start_time})" if hasattr(self, 'sync_start_time') and self.sync_start_time else ""
                sync_logger.info(f"【{self.account.email}】{time_label} IMAP连接成功")
                
                # 连接成功，清除失败计数
                cache_key = f"imap_fail_count:{self.account.id}"
                cache.delete(cache_key)
                return
            
            except imaplib.IMAP4.error as e:
                # IMAP特定错误（认证失败等）通常不需要重试
                error_msg = str(e)
                
                if 'LOGIN failed' in error_msg or 'authentication failed' in error_msg.lower():
                    detail_msg = (
                        f"IMAP登录认证失败 ({self.account.email})。\n"
                        "可能的原因：\n"
                        "1. 密码错误（很多邮箱需要使用「授权码」而非登录密码）\n"
                        "2. 邮箱未开启IMAP服务\n"
                        "3. 需要开启「第三方客户端」访问权限\n"
                        f"详细错误: {error_msg}"
                    )
                else:
                    detail_msg = f"IMAP操作失败: {error_msg}"
                
                raise HTTPException(status_code=http_status.HTTP_401_UNAUTHORIZED, detail=detail_msg)
            
            except (ssl.SSLError, socket.error, socket.timeout, ConnectionError, ConnectionResetError,
                    imaplib.IMAP4.abort, OSError, BrokenPipeError) as e:
                # 网络相关错误，可以重试
                last_error = e
                last_error_type = type(e).__name__
                
                if attempt < max_retries:
                    continue
            
            except Exception as e:
                # 捕获其他所有未知异常
                last_error = e
                last_error_type = type(e).__name__
                
                if attempt < max_retries:
                    continue
        
        # 所有重试都失败，记录失败次数
        if last_error:
            # 增加失败计数
            cache_key = f"imap_fail_count:{self.account.id}"
            fail_count = cache.get(cache_key) or 0
            fail_count += 1
            cache.set(cache_key, fail_count, expire=3600)  # 1小时过期
            
            # 只输出简洁的错误信息，不输出堆栈
            sync_logger.warning(f"【{self.account.email}】IMAP连接失败: {last_error_type} (连续失败{fail_count}次)")
            raise last_error

    def _keep_alive(self):
        """发送NOOP命令保持连接活跃（每30秒一次）- 已废弃，使用_smart_keep_alive"""
        self._smart_keep_alive()
    
    def _smart_keep_alive(self, force: bool = False):
        """
        【优化】智能保活：只在必要时发送NOOP
        
        Args:
            force: 强制发送NOOP（文件夹切换时使用）
        """
        import time
        if not hasattr(self, 'last_noop_time'):
            self.last_noop_time = time.time()
        
        current_time = time.time()
        time_label = f"({self.sync_start_time})" if hasattr(self, 'sync_start_time') and self.sync_start_time else ""
        
        # 【优化】距离上次操作超过45秒才NOOP（放宽到45秒）
        if force or (current_time - self.last_noop_time > 45):
            try:
                if self.imap:
                    self.imap.noop()
                    self.last_noop_time = current_time
            except (ssl.SSLEOFError, imaplib.IMAP4.abort) as e:
                self.imap = None
            except Exception as e:
                pass

    def _check_connection(self):
        """检查IMAP连接是否仍然有效"""
        if not self.imap:
            return False
        try:
            # 使用NOOP命令检查连接
            status, _ = self.imap.noop()
            return status == 'OK'
        except (ssl.SSLEOFError, imaplib.IMAP4.abort) as e:
            return False
        except Exception as e:
            return False

    def _ensure_connection(self):
        """确保IMAP连接有效，如果无效则重新连接"""
        if not self._check_connection():
            time_label = f"({self.sync_start_time})" if hasattr(self, 'sync_start_time') and self.sync_start_time else ""
            self._disconnect()
            self._connect()

    def _imap_operation_with_retry(self, operation_func, operation_name="IMAP操作", max_retries=2):
        """
        执行IMAP操作，支持超时重试和自动重连

        Args:
            operation_func: 要执行的操作函数（lambda或函数引用）
            operation_name: 操作名称，用于日志
            max_retries: 最大重试次数

        Returns:
            操作函数的返回值
        """
        import time

        last_error = None
        for attempt in range(max_retries + 1):
            try:
                if attempt > 0:
                    logger.info(f"{operation_name} 第 {attempt + 1}/{max_retries + 1} 次尝试")
                    # 重新连接
                    self._disconnect()
                    time.sleep(1)  # 短暂延迟
                    self._connect()
                
                # 【修复】在执行操作前，确保IMAP连接对象存在
                if not self.imap:
                    # 如果连接对象不存在，则将其视为连接中断，以便触发重试逻辑
                    raise imaplib.IMAP4.abort("IMAP connection object is None.")

                # 执行操作
                return operation_func()

            except (OSError, socket.timeout, socket.error) as e:
                last_error = e
                error_msg = str(e)
                pass

                if attempt < max_retries:
                    continue
                else:
                    # 最后一次尝试也失败了
                    raise

            except imaplib.IMAP4.abort as e:
                # IMAP连接中断
                last_error = e

                if attempt < max_retries:
                    continue
                else:
                    raise

            except Exception as e:
                # 其他异常不重试，直接抛出
                logger.error(f"{operation_name} 发生异常: {e}")
                raise

        # 如果所有重试都失败，抛出最后一个错误
        if last_error:
            raise last_error

    def _disconnect(self):
        """安全断开IMAP连接，处理超时和其他异常"""
        if self.imap:
            try:
                # 尝试正常登出
                self.imap.logout()
            except (ssl.SSLEOFError, imaplib.IMAP4.abort):
                # SSL连接已断开或IMAP连接中断，静默处理
                pass
            except (OSError, socket.timeout, socket.error) as e:
                # 连接已超时或断开，直接关闭socket
                try:
                    if hasattr(self.imap, 'socket') and self.imap.socket:
                        self.imap.socket().close()
                except Exception:
                    pass
            except Exception as e:
                # 其他异常也尝试关闭
                try:
                    if hasattr(self.imap, 'socket') and self.imap.socket:
                        self.imap.socket().close()
                except Exception:
                    pass
            finally:
                self.imap = None

    def create_folder(self, folder_name: str, parent_folder: str = None) -> bool:
        """
        在IMAP服务器上创建新文件夹。
        """
        try:
            db: Session = SessionLocal()
            account = db.query(EmailAccount).filter(EmailAccount.id == self.account_id).first()
            if not account:
                return False
            self.account = account
            self._connect()
            
            # 构建完整文件夹路径
            full_folder_name = f"{parent_folder}/{folder_name}" if parent_folder else folder_name
            
            # 获取所有服务器文件夹用于判断是否需要添加命名空间
            try:
                status, folder_list = self.imap.list('""', '*')
            except:
                status, folder_list = self.imap.list()
            
            all_server_folders = []
            if status == 'OK':
                for folder_data in folder_list:
                    line_str = folder_data.decode('utf-8', 'ignore')
                    match = re.match(r'\((.*?)\)\s+"(.*?)"\s+"?(.*?)"?$', line_str)
                    if match:
                        _, _, name = match.groups()
                        name = name.strip('"')
                        decoded = decode_modified_utf7(name)
                        all_server_folders.append(decoded)
            
            # 添加命名空间前缀（如果需要）
            server_folder_name = add_namespace_folder(full_folder_name, account.email, all_server_folders)
            encoded_folder_name = encode_modified_utf7(server_folder_name)
            
            status, _ = self.imap.create(f'"{encoded_folder_name}"')
            
            self._disconnect()
            return status == 'OK'
        except Exception as e:
            logger.error(f"创建文件夹失败: {e}")
            return False
        finally:
            db.close()

    def rename_folder(self, old_folder_name: str, new_folder_name: str) -> bool:
        """
        在IMAP服务器上重命名文件夹，并更新数据库中的相关邮件。
        如果有子文件夹，需要先重命名子文件夹，再重命名父文件夹。
        """
        db: Session = SessionLocal()
        try:
            account = db.query(EmailAccount).filter(EmailAccount.id == self.account_id).first()
            if not account:
                return False
            self.account = account
            self._connect()

            # 获取所有文件夹列表，找出所有子文件夹
            try:
                status, folder_list = self.imap.list('""', '*')
            except:
                status, folder_list = self.imap.list()
            if status != 'OK':
                logger.error("无法获取文件夹列表")
                return False
            
            # 收集所有需要重命名的文件夹（包括子文件夹）
            folders_to_rename = []
            old_prefix = f"{old_folder_name}/"
            
            for folder_data in folder_list:
                line_str = folder_data.decode('utf-8', 'ignore')
                # 支持两种格式：name有引号和无引号
                match = re.match(r'\((.*?)\)\s+"(.*?)"\s+"?(.*?)"?$', line_str)
                if match:
                    flags, delimiter, original_name = match.groups()
                    original_name = original_name.strip('"')  # 移除可能的引号
                    if "\\Noselect" in flags:
                        continue
                    
                    decoded_name = decode_modified_utf7(original_name)
                    # 剥离命名空间以匹配前端传来的名称
                    stripped_name = strip_namespace_folder(decoded_name, account.email)
                    
                    # 检查是否是要重命名的文件夹或其子文件夹
                    if stripped_name == old_folder_name:
                        folders_to_rename.append({
                            'decoded': decoded_name,  # 保留原始解码名称用于IMAP操作
                            'stripped': stripped_name,  # 剥离后的名称用于数据库更新
                            'encoded': original_name,
                            'is_parent': True,
                            'depth': stripped_name.count('/')
                        })
                    elif stripped_name.startswith(old_prefix):
                        folders_to_rename.append({
                            'decoded': decoded_name,
                            'stripped': stripped_name,
                            'encoded': original_name,
                            'is_parent': False,
                            'depth': stripped_name.count('/')
                        })
            
            # 按深度降序排序（先重命名最深层的子文件夹）
            folders_to_rename.sort(key=lambda x: x['depth'], reverse=True)
            
            logger.info(f"需要重命名 {len(folders_to_rename)} 个文件夹: {[f['decoded'] for f in folders_to_rename]}")
            
            # 逐个重命名文件夹
            renamed_folders = []
            
            # 构建已重命名文件夹的映射（用于计算子文件夹的新路径）
            rename_mapping = {}  # stripped_name -> new_stripped_name
            
            for folder_info in folders_to_rename:
                old_name_decoded = folder_info['decoded']  # 服务器原始名称
                old_name_stripped = folder_info['stripped']  # 剥离后的名称
                old_name_encoded = folder_info['encoded']
                
                # 计算新名称（剥离后的）
                if folder_info['is_parent']:
                    new_name_stripped = new_folder_name
                else:
                    # 子文件夹：需要考虑父文件夹已经被重命名的情况
                    # 从旧路径开始，逐层替换已重命名的部分
                    path_parts = old_name_stripped.split('/')
                    new_path_parts = []
                    
                    for i, part in enumerate(path_parts):
                        # 重建当前层级的路径
                        current_path = '/'.join(path_parts[:i+1])
                        
                        # 检查这个路径是否在映射中（已经被重命名）
                        if current_path in rename_mapping:
                            # 使用新名称
                            mapped_path = rename_mapping[current_path]
                            mapped_parts = mapped_path.split('/')
                            new_path_parts.append(mapped_parts[-1])
                        else:
                            # 使用原名称
                            new_path_parts.append(part)
                    
                    new_name_stripped = '/'.join(new_path_parts)
                
                # 记录映射关系（剥离后的名称）
                rename_mapping[old_name_stripped] = new_name_stripped
                
                # 为服务器操作添加命名空间前缀（如果需要）
                # 获取当前所有服务器文件夹
                all_server_folders = []
                for fd in folder_list:
                    ls = fd.decode('utf-8', 'ignore')
                    m = re.match(r'\((.*?)\)\s+"(.*?)"\s+"?(.*?)"?$', ls)
                    if m:
                        _, _, n = m.groups()
                        n = n.strip('"')
                        all_server_folders.append(decode_modified_utf7(n))
                
                new_name_with_ns = add_namespace_folder(new_name_stripped, account.email, all_server_folders)
                new_name_encoded = encode_modified_utf7(new_name_with_ns)
                
                logger.info(f"重命名: '{old_name_stripped}' -> '{new_name_stripped}' (服务器: '{old_name_decoded}' -> '{new_name_with_ns}')")
                
                # 执行IMAP重命名
                status, response = self.imap.rename(f'"{old_name_encoded}"', f'"{new_name_encoded}"')
                
                if status != 'OK':
                    logger.error(f"IMAP重命名失败: {old_name_decoded} -> {new_name_decoded}, 响应: {response}")
                    # 如果重命名失败，回滚已经重命名的文件夹
                    for renamed in reversed(renamed_folders):
                        try:
                            self.imap.rename(f'"{renamed["new_encoded"]}"', f'"{renamed["old_encoded"]}"')
                            logger.warning(f"回滚: {renamed['new_decoded']} -> {renamed['old_decoded']}")
                        except Exception as e:
                            logger.error(f"回滚失败: {e}")
                    return False
                
                renamed_folders.append({
                    'old_decoded': old_name_decoded,
                    'new_decoded': new_name_with_ns,
                    'old_stripped': old_name_stripped,
                    'new_stripped': new_name_stripped,
                    'old_encoded': old_name_encoded,
                    'new_encoded': new_name_encoded
                })

            self._disconnect()

            # 更新数据库中的所有相关邮件
            try:
                with db.begin_nested():
                    for renamed in renamed_folders:
                        old_name = renamed['old_stripped']  # 使用剥离后的名称
                        new_name = renamed['new_stripped']
                        
                        # 【v2.0改造】更新EmailFolder关联表中的文件夹名称
                        db.query(EmailFolder).filter(
                            EmailFolder.email_account_id == self.account_id,
                            EmailFolder.folder_name == old_name
                        ).update({"folder_name": new_name}, synchronize_session=False)

                db.commit()
                logger.info(f"文件夹 '{old_folder_name}' 及其所有子文件夹已成功重命名为 '{new_folder_name}'")
                return True
                
            except Exception as e:
                logger.error(f"更新数据库时出错: {e}")
                db.rollback()
                return False
                
        except Exception as e:
            logger.error(f"重命名文件夹时发生异常: {e}", exc_info=True)
            db.rollback()
            return False
        finally:
            db.close()

    def _get_folders_to_sync(self):
        folders_to_sync = {}
        folder_mapping = {}  # 用于缓存的文件夹映射
        time_label = f"({self.sync_start_time})" if hasattr(self, 'sync_start_time') and self.sync_start_time else ""

        try:
            # 使用重试机制获取文件夹列表
            def list_folders():
                try:
                    return self.imap.list('""', '*')
                except:
                    return self.imap.list()
            
            status, folder_list = self._imap_operation_with_retry(
                list_folders,
                operation_name="获取文件夹列表"
            )
            
            if status == 'OK':
                if not folder_list:
                    sync_logger.warning(f"【{self.account.email}】{time_label} 文件夹列表为空")
                    return []
                
                for folder_data in folder_list:
                    try:
                        line_str = folder_data.decode('utf-8', 'ignore')
                        # 支持两种格式：name有引号和无引号
                        # 格式1: (\flags) "." "FolderName"
                        # 格式2: (\flags) "." FolderName
                        match = re.match(r'\((.*?)\)\s+"(.*?)"\s+"?(.*?)"?$', line_str)
                        if match:
                            flags, delimiter, original_name = match.groups()
                            original_name = original_name.strip('"')  # 移除可能的引号
                            
                            # 【新增】跳过草稿文件夹
                            if r'\Drafts' in flags:
                                continue

                            if "\\Noselect" not in flags:
                                decoded_name = decode_modified_utf7(original_name)
                                
                                # 【新增】根据名称跳过草稿文件夹
                                if decoded_name.lower() in ["drafts", "draft", "草稿", "草稿箱"]:
                                    continue
                                
                                # 【新增】跳过星标/旗标文件夹，因为它们通常是虚拟的
                                if decoded_name.lower() in ["starred", "flagged", "已加星标"]:
                                    continue
                                
                                # 添加特殊文件夹名称映射
                                # 记录原始名称以便后续反向查找
                                if r'\Sent' in flags:
                                    decoded_name = "Sent"
                                elif r'\Junk' in flags:
                                    decoded_name = "Junk"
                                elif r'\Trash' in flags:
                                    decoded_name = "Trash"
                                # 基于文件夹名称的映射（兼容更多邮件服务商）
                                elif decoded_name in ["Sent Messages", "Sent Items", "已发送邮件", "已发送"]:
                                    decoded_name = "Sent"
                                elif decoded_name in ["Deleted Messages", "Deleted Items", "已删除邮件", "已删除"]:
                                    decoded_name = "Trash"
                                
                                # 剥离命名空间文件夹（使用邮箱域名严格匹配）
                                stripped_name = strip_namespace_folder(decoded_name, self.account.email)
                                
                                # 【修复】跳过虚拟文件夹本身（如果剥离后为空或就是命名空间文件夹本身）
                                # 从配置中获取命名空间文件夹列表
                                from api.utils.folder_helper import get_provider_by_email
                                provider = get_provider_by_email(self.account.email)
                                if provider:
                                    namespace_folders = provider.get('namespace_folders', [])
                                    # 如果剥离前后相同，说明这就是命名空间文件夹本身，应该跳过
                                    if decoded_name in namespace_folders and stripped_name == decoded_name:
                                        sync_logger.debug(f"【{self.account.email}】{time_label} 跳过虚拟文件夹: {decoded_name}")
                                        continue
                                
                                folders_to_sync[original_name] = {
                                    "original": original_name,
                                    "decoded": stripped_name,  # 使用剥离后的名称
                                    "flags": flags
                                }
                                
                                # 构建文件夹映射（用于缓存）- 使用剥离后的名称作为key
                                folder_mapping[stripped_name] = {
                                    "display_name": stripped_name,
                                    "encoded_name": original_name,
                                    "flags": flags
                                }
                        else:
                            sync_logger.warning(f"【{self.account.email}】{time_label} 无法解析文件夹行: {line_str[:100]}")
                            continue
                    except Exception as parse_error:
                        sync_logger.error(f"【{self.account.email}】{time_label} 解析文件夹数据失败: {parse_error}")
                        continue
                
                # 【优化】同步时更新文件夹缓存
                if folder_mapping:
                    self._update_folders_cache(folder_mapping)
            else:
                sync_logger.error(f"【{self.account.email}】{time_label} 获取文件夹列表失败: status={status}")
                    
        except Exception as e:
            sync_logger.error(f"【{self.account.email}】{time_label} 获取文件夹列表异常: {type(e).__name__} - {e}")
            logger.error(f"【{self.account.email}】获取文件夹列表详细错误", exc_info=True)
        
        folder_count = len(folders_to_sync)
        return list(folders_to_sync.values())
    
    def _cleanup_deleted_folders(self, server_folders: list, db: Session):
        """
        检测并清理服务器上已删除的文件夹及其关联的邮件
        
        Args:
            server_folders: 服务器返回的文件夹列表
            db: 数据库会话
        """
        time_label = f"({self.sync_start_time})" if self.sync_start_time else ""
        
        try:
            # 1. 获取服务器上存在的文件夹名称集合（使用解码后的名称）
            server_folder_names = set()
            for folder_info in server_folders:
                decoded_name = folder_info.get("decoded")
                if decoded_name:
                    server_folder_names.add(decoded_name)
            
            # 2. 查询数据库中当前账户的所有文件夹关联记录
            db_folders = db.query(EmailFolder.folder_name).filter(
                EmailFolder.email_account_id == self.account.id
            ).distinct().all()
            
            db_folder_names = {folder[0] for folder in db_folders}
            
            # 3. 找出已删除的文件夹（存在于数据库但不存在于服务器）
            # 排除虚拟文件夹（UNREAD等）
            virtual_folders = {"UNREAD", "STARRED", "FLAGGED"}
            deleted_folders = db_folder_names - server_folder_names - virtual_folders
            
            if not deleted_folders:
                return
            
            sync_logger.info(f"【{self.account.email}】{time_label} 检测到 {len(deleted_folders)} 个已删除的文件夹: {deleted_folders}")
            
            # 4. 对每个已删除的文件夹，清理其关联记录
            total_relations_deleted = 0
            total_orphan_emails_deleted = 0
            
            for folder_name in deleted_folders:
                # 获取该文件夹下的所有关联记录
                folder_relations = db.query(EmailFolder).filter(
                    EmailFolder.email_account_id == self.account.id,
                    EmailFolder.folder_name == folder_name
                ).all()
                
                if not folder_relations:
                    continue
                
                # 收集需要检查的邮件ID
                email_ids_to_check = set()
                
                # 删除文件夹关联记录
                for relation in folder_relations:
                    email_ids_to_check.add(relation.email_id)
                    db.delete(relation)
                    total_relations_deleted += 1
                
                db.flush()
                
                # 检查并清理孤立邮件（没有任何文件夹关联的邮件）
                for email_id in email_ids_to_check:
                    # 检查该邮件是否还有其他文件夹关联
                    remaining_relations = db.query(EmailFolder).filter(
                        EmailFolder.email_id == email_id
                    ).count()
                    
                    if remaining_relations == 0:
                        # 邮件已成为孤立邮件，删除邮件记录和EML文件
                        email_record = db.query(Email).filter(Email.id == email_id).first()
                        if email_record:
                            # 删除EML文件
                            if email_record.eml_path and os.path.exists(email_record.eml_path):
                                try:
                                    os.remove(email_record.eml_path)
                                    sync_logger.debug(f"【{self.account.email}】删除孤立邮件的EML文件: {email_record.eml_path}")
                                except OSError as e:
                                    logger.error(f"删除EML文件失败 {email_record.eml_path}: {e}")
                            
                            # 删除翻译文件（如果存在）
                            if email_record.eml_path:
                                translated_file_path = email_record.eml_path.rsplit('.', 1)[0] + '_zh.html'
                                if os.path.exists(translated_file_path):
                                    try:
                                        os.remove(translated_file_path)
                                        sync_logger.debug(f"【{self.account.email}】删除孤立邮件的翻译文件: {translated_file_path}")
                                    except OSError as e:
                                        logger.error(f"删除翻译文件失败 {translated_file_path}: {e}")
                            
                            # 删除邮件记录（级联删除会自动删除所有关联）
                            db.delete(email_record)
                            total_orphan_emails_deleted += 1
                
                sync_logger.info(
                    f"【{self.account.email}】{time_label} 文件夹 '{folder_name}': "
                    f"删除 {len(folder_relations)} 条关联记录"
                )
            
            # 提交数据库更改
            db.commit()
            
            sync_logger.info(
                f"【{self.account.email}】{time_label} 文件夹清理完成: "
                f"共删除 {total_relations_deleted} 条关联记录，清理 {total_orphan_emails_deleted} 封孤立邮件"
            )
            
        except Exception as e:
            sync_logger.error(f"【{self.account.email}】{time_label} 清理已删除文件夹时出错: {e}")
            logger.error(f"清理已删除文件夹详细错误", exc_info=True)
            db.rollback()
    
    def _update_folders_cache(self, folder_mapping: dict):
        """
        同步时更新文件夹缓存
        
        Args:
            folder_mapping: 文件夹映射字典 {decoded_name: {display_name, encoded_name, flags}}
        """
        try:
            cache_key = f"account_folders:{self.account.id}"
            
            # 构建与get_folders_list()相同的最终文件夹列表
            final_folders = self._build_folder_tree_from_mapping(folder_mapping)
            
            # 构建缓存数据（与get_folders_list()保持一致）
            cache_data = {
                "folder_mapping": folder_mapping,
                "final_folders": final_folders
            }
            
            # 更新缓存（24小时过期）
            cache.set(cache_key, cache_data, expire=86400)
            
            pass
        except Exception as e:
            # 缓存更新失败不应影响邮件同步
            logger.error(f"更新文件夹缓存失败: {e}")
    
    def _build_folder_tree_from_mapping(self, folder_mapping: dict) -> list:
        """
        从文件夹映射构建层级结构的文件夹树（用于同步服务缓存）
        
        Args:
            folder_mapping: 文件夹映射字典
            
        Returns:
            最终的文件夹列表（包含层级结构）
        """
        # 固定文件夹
        fixed_folders_top = [
            {"name": "INBOX", "display_name": "收件箱"},
            {"name": "UNREAD", "display_name": "未读邮件"},
        ]
        
        fixed_folders_bottom = [
            {"name": "Drafts", "display_name": "草稿箱"},
            {"name": "Sent", "display_name": "已发送"},
            {"name": "Junk", "display_name": "垃圾邮件"},
            {"name": "Trash", "display_name": "已删除"},
        ]
        
        # 用于过滤的固定文件夹名称集合
        fixed_folder_names = {"INBOX", "UNREAD", "SENT", "JUNK", "TRASH", "草稿", "DRAFTS", "DRAFT"}
        
        # 过滤自定义文件夹
        custom_folders_map = {}
        for folder_name, folder_info in folder_mapping.items():
            folder_name_upper = folder_name.upper()
            if folder_name_upper in fixed_folder_names:
                continue
            if "草稿" in folder_name or "DRAFT" in folder_name_upper:
                continue
            custom_folders_map[folder_name] = folder_info

        # 构建自定义文件夹的层级结构
        root_folders = {}
        # 按路径排序，确保父文件夹先于子文件夹被创建
        sorted_custom_paths = sorted(custom_folders_map.keys())

        for full_path in sorted_custom_paths:
            # 假设 '/' 是分隔符，这是最常见的IMAP分隔符
            parts = full_path.split('/')
            current_level = root_folders
            
            for i, part in enumerate(parts):
                if part not in current_level:
                    current_path = '/'.join(parts[:i+1])
                    current_level[part] = {
                        "name": current_path,
                        "display_name": part,
                        "children": {}
                    }
                current_level = current_level[part]["children"]

        # 将树形结构转换为列表格式
        def build_list(tree):
            result = []
            # 按key排序以保证文件夹顺序稳定
            for key, value in sorted(tree.items()):
                item = {
                    "name": value["name"],
                    "display_name": value["display_name"]
                }
                if value["children"]:
                    item["children"] = build_list(value["children"])
                result.append(item)
            return result

        custom_folders_tree = build_list(root_folders)

        # 组装最终的文件夹列表
        final_folders = fixed_folders_top + custom_folders_tree + fixed_folders_bottom
        
        return final_folders

    def _sync_folder_only(self, folder_info: dict, db: Session):
        """
        仅同步指定的单个文件夹（用于刷新按钮）
        这个方法会自动管理IMAP连接
        """
        try:
            self._connect()
            self._sync_folder(folder_info, db)
        except Exception as e:
            logger.error(f"同步文件夹 '{folder_info.get('decoded', 'unknown')}' 失败: {e}", exc_info=True)
            raise
        finally:
            self._disconnect()

    def _sync_folder(self, folder_info: dict, db: Session):
        """
        统一同步逻辑（多对多关系）
        基于 Message-ID 进行邮件去重，使用 EmailFolder 关联表管理邮件与文件夹的关系
        """
        original_folder_name = folder_info["original"]
        decoded_folder_name = folder_info["decoded"]
        newly_synced_emails = []
        
        time_label = f"({self.sync_start_time})" if hasattr(self, 'sync_start_time') and self.sync_start_time else ""
        
        import time
        # 使用重试机制选择文件夹
        try:
            t_select_start = time.perf_counter()
            status, data = self._imap_operation_with_retry(
                lambda: self.imap.select(f'"{original_folder_name}"', readonly=True),
                operation_name=f"选择文件夹 {decoded_folder_name}"
            )
            if status != 'OK':
                # 解析data获取错误信息
                error_msg = data[0].decode('utf-8', 'ignore') if data and len(data) > 0 and isinstance(data[0], bytes) else str(data)
                
                # SELECT失败时自动尝试读写模式（不再依赖error_msg内容判断）
                try:
                    status2, data2 = self._imap_operation_with_retry(
                        lambda: self.imap.select(f'"{original_folder_name}"', readonly=False),
                        operation_name=f"选择文件夹(读写模式) {decoded_folder_name}"
                    )
                    if status2 == 'OK':
                        status, data = status2, data2
                    else:
                        return None, []
                except Exception as e2:
                    sync_logger.error(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 读写模式异常: {e2}")
                    return None, []
            t_select_end = time.perf_counter()
            sync_logger.info(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 选择文件夹耗时: {t_select_end - t_select_start:.4f}s")
        except Exception as e:
            sync_logger.error(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 选择文件夹异常: {type(e).__name__} - {e}")
            logger.error(f"选择文件夹 {decoded_folder_name} 失败: {e}", exc_info=True)
            return None, []
        
        # 获取当前文件夹的UIDVALIDITY
        try:
            current_uidvalidity = get_uidvalidity(
                self.imap, data, original_folder_name, decoded_folder_name, self.account.id
            )
        except Exception as e:
            sync_logger.error(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 获取UIDVALIDITY失败: {type(e).__name__} - {e}")
            logger.error(f"获取文件夹 {decoded_folder_name} 的UIDVALIDITY失败: {e}", exc_info=True)
            return None, []
        
        # 使用重试机制搜索邮件
        try:
            t_search_start = time.perf_counter()
            status, uids_data = self._imap_operation_with_retry(
                lambda: self.imap.uid('search', None, "ALL"),
                operation_name=f"搜索文件夹 {decoded_folder_name} 中的邮件"
            )
            t_search_end = time.perf_counter()
            if status == 'OK':
                num_uids = len(uids_data[0].split())
                sync_logger.info(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 搜索到 {num_uids} 封邮件, 耗时: {t_search_end - t_search_start:.4f}s")
            else:
                # 增加对 SELECT 状态的检查
                if self.imap.state != 'SELECTED':
                    sync_logger.error(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 搜索邮件失败: 文件夹未被成功选中 (当前状态: {self.imap.state})")
                else:
                    sync_logger.error(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 搜索邮件失败, 耗时: {t_search_end - t_search_start:.4f}s")
                return None, []
        except Exception as e:
            logger.error(f"搜索文件夹 {decoded_folder_name} 中的邮件失败: {e}")
            return None, []
        
        server_uids = set(uids_data[0].split())
        
        # 【诊断日志】记录SEARCH返回的UID数量和详情
        time_label = f"({self.sync_start_time})" if hasattr(self, 'sync_start_time') and self.sync_start_time else ""
        if len(server_uids) > 0 and len(server_uids) <= 10:
            pass
        elif len(server_uids) > 10:
            # UID较多时，只记录范围
            pass

        # 从数据库获取当前文件夹下的所有关联记录
        existing_folder_relations = db.query(EmailFolder).filter(
            EmailFolder.email_account_id == self.account.id,
            EmailFolder.folder_name == decoded_folder_name
        ).all()
        
        # 构建 UID -> EmailFolder 的映射
        existing_uids_map = {ef.uid.encode(): ef for ef in existing_folder_relations}
        existing_uids = set(existing_uids_map.keys())

        # 1. 删除服务器上不存在的关联（级联删除逻辑）
        uids_to_delete = existing_uids - server_uids
        if uids_to_delete:
            delete_folder_relations(
                uids_to_delete, existing_uids_map, decoded_folder_name, self.account.email, db
            )

        # 2. 找出需要同步的新邮件 UIDs
        new_uids = sorted(list(server_uids - existing_uids), key=lambda x: int(x))
        
        time_label = f"({self.sync_start_time})" if hasattr(self, 'sync_start_time') and self.sync_start_time else ""
        if new_uids:
            sync_logger.info(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 发现 {len(new_uids)} 封新邮件，开始同步...")
        else:
            return 0, []
        
        # 3. 分批处理新邮件【优化：批次大小从50改为15，使用批量FETCH】
        fetch_batch_size = 15  # 批量FETCH批次大小（代理环境建议10-15）
        batches = [new_uids[i:i + fetch_batch_size] for i in range(0, len(new_uids), fetch_batch_size)]
        total_batches = len(batches)
        
        # 统计信息
        batch_success_count = 0
        batch_fail_count = 0
        
        for batch_idx, batch_uids in enumerate(batches, 1):
            batch_emails = []  # 本批次成功的邮件
            
            # 【优化】批次开始前NOOP保活（替代每5封的频繁NOOP）
            try:
                self._smart_keep_alive(force=True)
            except Exception as e:
                pass
            
            # 【核心优化】尝试批量获取邮件
            t_batch_fetch_start = time.perf_counter()
            emails_data_list = self._batch_fetch_emails(batch_uids, decoded_folder_name, current_uidvalidity)
            t_batch_fetch_end = time.perf_counter()
            
            if emails_data_list:
                sync_logger.info(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 批次 {batch_idx}/{total_batches} (共 {len(batch_uids)} 封) 批量获取成功, 耗时: {t_batch_fetch_end - t_batch_fetch_start:.4f}s")
                # 批量获取成功，处理每封邮件
                
                t_processing_start = time.perf_counter()
                for email_data in emails_data_list:
                    uid_str = email_data.get('uid')
                    
                    try:
                        # 提取邮件元数据
                        message_id = email_data.get("message_id")
                        flags = email_data.pop('flags', [])
                        text_body = email_data.pop('text_body', None)
                        html_body = email_data.pop('html_body', None)
                        raw_email_data = email_data.pop('raw_email_data', None)
                        has_attachments = email_data.get('has_attachments', False)
                        attachment_count = email_data.pop('attachment_count', 0)
                        
                        # 生成摘要
                        email_data["summary"] = self._create_summary(text_body, html_body, has_attachments, attachment_count)
                        email_data["is_read"] = '\\Seen' in flags
                        
                        # 【修复】移除已迁移到EmailFolder表的字段，避免数据库约束错误
                        email_data.pop('uid', None)
                        email_data.pop('folder', None)
                        email_data.pop('uidvalidity', None)
                        email_data.pop('imap_key', None)
                        
                        # 基于Message-ID查找或创建邮件记录
                        email_record = find_or_create_email_by_message_id(
                            message_id=message_id,
                            email_account_id=self.account.id,
                            email_data=email_data,
                            db=db
                        )
                        
                        # 创建或更新文件夹关联
                        flags_str = ' '.join(flags) if flags else ''
                        create_or_update_folder_relation(
                            email_id=email_record.id,
                            email_account_id=self.account.id,
                            folder_name=decoded_folder_name,
                            uid=uid_str,
                            uidvalidity=current_uidvalidity,
                            flags=flags_str,
                            db=db
                        )
                        
                        failed_email_tracker.clear_failure(self.account.id, decoded_folder_name, uid_str)
                        batch_emails.append(email_record)
                        
                        # 清理临时变量
                        del text_body, html_body, raw_email_data, email_data
                        
                    except Exception as e:
                        count = failed_email_tracker.record_failure(
                            self.account.id, decoded_folder_name, uid_str, f"处理异常: {str(e)}"
                        )
                        sync_logger.error(f"【{self.account.email}】{time_label} [{decoded_folder_name}] UID {uid_str} 处理异常（第{count}次）: {e}")
            else:
                sync_logger.warning(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 批次 {batch_idx}/{total_batches} 批量获取失败, 耗时: {t_batch_fetch_end - t_batch_fetch_start:.4f}s, 回退到逐封处理")
                # 批量获取失败，回退到逐封处理
                
                for uid in batch_uids:
                    uid_str = uid.decode() if isinstance(uid, bytes) else str(uid)
                    
                    # 检查是否应该跳过
                    if failed_email_tracker.should_skip(self.account.id, decoded_folder_name, uid_str):
                        sync_logger.error(f"【{self.account.email}】{time_label} [{decoded_folder_name}] UID {uid_str} 失败次数过多，跳过同步")
                        continue
                    
                    try:
                        saved_email = self._sync_single_email(uid, decoded_folder_name, current_uidvalidity, db)
                        if saved_email:
                            failed_email_tracker.clear_failure(self.account.id, decoded_folder_name, uid_str)
                            batch_emails.append(saved_email)
                        else:
                            count = failed_email_tracker.record_failure(
                                self.account.id, decoded_folder_name, uid_str, "获取邮件数据失败"
                            )
                    except Exception as e:
                        count = failed_email_tracker.record_failure(
                            self.account.id, decoded_folder_name, uid_str, f"同步异常: {str(e)}"
                        )
                        sync_logger.error(f"【{self.account.email}】{time_label} [{decoded_folder_name}] UID {uid_str} 同步异常（第{count}次）: {e}")
            
            # 批次处理完成后提交数据库（使用重试机制，最多重试10次）
            commit_success = False
            max_commit_retries = 10
            
            for retry_attempt in range(max_commit_retries):
                try:
                    @retry_on_db_lock(max_retries=5, delay=2, backoff=2)
                    def commit_batch():
                        db.commit()
                    
                    t_commit_start = time.perf_counter()
                    commit_batch()
                    t_commit_end = time.perf_counter()
                    commit_success = True
                    
                    # 提交成功后，将本批次邮件加入总列表
                    newly_synced_emails.extend(batch_emails)
                    sync_logger.info(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 批次 {batch_idx}/{total_batches} 提交数据库成功, 耗时: {t_commit_end - t_commit_start:.4f}s")
                    batch_success_count += 1
                    
                    break
                    
                except Exception as e:
                    if retry_attempt < max_commit_retries - 1:
                        import time
                        wait_time = 3 * (retry_attempt + 1)  # 递增等待时间：3s, 6s, 9s...
                        db.rollback()
                        time.sleep(wait_time)
                    else:
                        sync_logger.error(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 第 {batch_idx}/{total_batches} 批提交失败（已重试{max_commit_retries}次），跳过本批: {e}")
                        logger.error(f"【{self.account.email}】{time_label} [{decoded_folder_name}] 提交批数据失败详情", exc_info=True)
                        db.rollback()
                        batch_fail_count += 1
                        break
            
            # 如果提交失败且是最后几批，记录警告
            pass
        
        # 输出批次处理统计信息
        if batch_fail_count > 1:
            sync_logger.warning(
                f"【{self.account.email}】{time_label} [{decoded_folder_name}] "
                f"批次处理完成：成功 {batch_success_count}/{total_batches} 批，失败 {batch_fail_count} 批"
            )
        
        return len(newly_synced_emails), newly_synced_emails

    def _batch_fetch_emails(self, uid_list: list, folder_name: str, uidvalidity: str) -> list:
        """
        批量获取多封邮件（一次网络请求获取多封）
        
        Args:
            uid_list: UID列表（bytes类型）
            folder_name: 文件夹名称
            uidvalidity: UIDVALIDITY
        
        Returns:
            list: 邮件数据字典列表
        """
        if not uid_list:
            return []
        
        time_label = f"({self.sync_start_time})" if hasattr(self, 'sync_start_time') and self.sync_start_time else ""
        
        # 将UID列表转换为IMAP格式
        if len(uid_list) == 1:
            uid_str = uid_list[0].decode()
        else:
            # 检查是否连续，使用范围格式（更高效）
            uids_int = [int(uid.decode()) for uid in uid_list]
            if max(uids_int) - min(uids_int) == len(uids_int) - 1:
                # 连续的UID，使用范围格式
                uid_str = f"{min(uids_int)}:{max(uids_int)}"
            else:
                # 不连续，使用逗号分隔
                uid_str = ','.join([uid.decode() for uid in uid_list])
        
        try:
            # 批量FETCH
            status, msg_data = self.imap.uid('fetch', uid_str, '(FLAGS BODY.PEEK[])')
            
            if status != 'OK' or not msg_data:
                return []
            
            # 解析批量响应
            emails_data = []
            for item in msg_data:
                if isinstance(item, tuple) and len(item) >= 2:
                    try:
                        # 提取UID
                        part_info = item[0].decode('utf-8', 'ignore')
                        uid_match = re.search(r'UID (\d+)', part_info)
                        if not uid_match:
                            continue
                        
                        current_uid = uid_match.group(1)
                        
                        # 提取FLAGS
                        flags = []
                        flags_match = re.search(r'FLAGS \((.*?)\)', part_info)
                        if flags_match:
                            flags = flags_match.group(1).split()
                        
                        # 提取邮件数据
                        raw_email_data = item[1]
                        if not raw_email_data:
                            continue
                        
                        # 解析邮件
                        msg = email.message_from_bytes(raw_email_data)
                        email_metadata = self._parse_email_metadata(msg, current_uid, folder_name, uidvalidity, None)
                        
                        if email_metadata:
                            email_metadata['flags'] = flags
                            text_body, html_body = self._get_email_body(msg)
                            email_metadata['text_body'] = text_body
                            email_metadata['html_body'] = html_body
                            email_metadata['raw_email_data'] = raw_email_data
                            emails_data.append(email_metadata)
                    
                    except Exception as e:
                        sync_logger.error(f"【{self.account.email}】{time_label} [{folder_name}] 解析批量响应项失败: {e}")
                        continue
            
            return emails_data
            
        except Exception as e:
            sync_logger.error(f"【{self.account.email}】{time_label} [{folder_name}] 批量FETCH异常: {e}")
            return []

    def _sync_single_email(self, uid: bytes, folder_name: str, uidvalidity: str, db: Session):
        """
        同步单封邮件（多对多关系版本）
        
        Args:
            uid: 邮件UID（bytes）
            folder_name: 文件夹名称
            uidvalidity: UIDVALIDITY
            db: 数据库会话
        
        Returns:
            Email对象或None
        """
        uid_str = uid.decode() if isinstance(uid, bytes) else str(uid)
        
        # 1. 获取邮件数据
        email_data = self._fetch_and_process_email(uid, self.imap, folder_name, uidvalidity, db)
        if not email_data:
            return None
        
        # 2. 提取邮件元数据
        message_id = email_data.get("message_id")
        flags = email_data.pop('flags', [])
        text_body = email_data.pop('text_body', None)
        html_body = email_data.pop('html_body', None)
        raw_email_data = email_data.pop('raw_email_data', None)
        has_attachments = email_data.get('has_attachments', False)
        attachment_count = email_data.pop('attachment_count', 0)
        
        # 3. 生成摘要
        email_data["summary"] = self._create_summary(text_body, html_body, has_attachments, attachment_count)
        email_data["is_read"] = '\\Seen' in flags
        
        # 【修复】移除已迁移到EmailFolder表的字段，避免数据库约束错误
        email_data.pop('uid', None)
        email_data.pop('folder', None)
        email_data.pop('uidvalidity', None)
        email_data.pop('imap_key', None)
        
        # 4. 基于 Message-ID 查找或创建邮件记录
        email_record = find_or_create_email_by_message_id(
            message_id=message_id,
            email_account_id=self.account.id,
            email_data=email_data,
            db=db
        )
        
        # 5. 创建或更新文件夹关联
        flags_str = ' '.join(flags) if flags else ''
        create_or_update_folder_relation(
            email_id=email_record.id,
            email_account_id=self.account.id,
            folder_name=folder_name,
            uid=uid_str,
            uidvalidity=uidvalidity,
            flags=flags_str,
            db=db
        )
        
        # 清理临时变量
        del text_body, html_body, raw_email_data, email_data
        
        return email_record

        return len(new_uids), newly_synced_emails

    def _fetch_and_process_email(self, uid: bytes, imap_conn, folder: str = "", uidvalidity: str = "", db: Session = None):
        """
        获取邮件的元数据和正文预览。
        这个版本获取完整的邮件结构来正确解析内容和附件，但只处理正文部分以生成摘要。
        同时返回原始邮件数据用于保存EML文件。
        """
        import socket
        
        fetch_command = '(FLAGS BODY.PEEK[])'  # 获取完整的邮件，以便正确解析
        max_fetch_retries = 3  # 最多重试3次
        
        for retry in range(max_fetch_retries):
            try:
                # 设置socket超时（120秒，代理连接需要更长时间）
                if hasattr(imap_conn, 'sock') and imap_conn.sock:
                    imap_conn.sock.settimeout(120)
                
                # 记录开始获取
                uid_str = uid.decode() if isinstance(uid, bytes) else str(uid)
                status, msg_data = imap_conn.uid('fetch', uid, fetch_command)
                
                # 记录获取结果
                if status != 'OK' or not msg_data or msg_data[0] is None:
                    if retry < max_fetch_retries - 1:
                        import time
                        time.sleep(2 * (retry + 1))  # 递增等待：2s, 4s, 6s
                        continue
                    else:
                        logger.error(f"获取 UID {uid.decode()} 失败（已重试{max_fetch_retries}次）: status={status}")
                        return None
                
                # 成功获取，跳出重试循环
                break
                
            except (socket.timeout, socket.error, imaplib.IMAP4.abort, imaplib.IMAP4.error, OSError) as e:
                error_type = type(e).__name__
                uid_str = uid.decode() if isinstance(uid, bytes) else str(uid)
                if retry < max_fetch_retries - 1:
                    import time
                    time.sleep(5 * (retry + 1))  # 递增等待：5s, 10s, 15s（代理需要更长时间）
                    
                    # 尝试重新连接IMAP
                    try:
                        sync_logger.error(f"尝试重新建立IMAP连接（重试 {retry + 1}）")
                        self._ensure_connection()
                    except Exception as reconnect_err:
                        sync_logger.error(f"重新连接IMAP失败: {reconnect_err}")
                else:
                    sync_logger.error(f"获取 UID {uid_str} 网络异常（已重试{max_fetch_retries}次）: {error_type} - {e}")
                    return None
            except Exception as e:
                error_type = type(e).__name__
                logger.error(f"获取 UID {uid.decode()} 的完整邮件失败: {error_type} - {e}")
                return None
        else:
            # 如果循环正常结束（没有break），说明所有重试都失败了
            logger.error(f"获取 UID {uid.decode()} 失败：所有重试均失败")
            return None

        flags = []
        raw_email_data = None

        # 从响应中提取标记和原始邮件数据
        for part in msg_data:
            if isinstance(part, tuple):
                # 提取 FLAGS
                part_info = part[0].decode('utf-8', 'ignore')
                flags_match = re.search(r'FLAGS \((.*?)\)', part_info)
                if flags_match:
                    flags = flags_match.group(1).split()
                
                # 提取原始邮件数据
                if len(part) > 1 and isinstance(part[1], bytes):
                    raw_email_data = part[1]
                    break
        
        if not raw_email_data:
             # 如果在元组中找不到，有时它作为列表中的一个独立项
            for part in msg_data:
                if isinstance(part, bytes):
                    raw_email_data = part
                    break

        if raw_email_data:
            try:
                msg = email.message_from_bytes(raw_email_data)
                
                # 解析元数据（现在附件检查会更准确）
                email_metadata = self._parse_email_metadata(msg, uid.decode(), folder, uidvalidity, db)
                
                if email_metadata:
                    email_metadata['flags'] = flags
                    
                    # 解析正文以生成摘要
                    text_body, html_body = self._get_email_body(msg)
                    
                    # 将正文添加到元数据中，以便后续处理
                    email_metadata['text_body'] = text_body
                    email_metadata['html_body'] = html_body
                    
                    # 保存原始邮件数据用于后续保存EML文件
                    email_metadata['raw_email_data'] = raw_email_data
                
                return email_metadata
            except Exception as e:
                sync_logger.error(f"解析 UID {uid.decode()} 的邮件失败: {e}")
                return None
        
        return None

    def _get_email_body(self, msg: email.message.Message):
        text_body = ""
        html_body = ""

        if msg.is_multipart():
            for part in msg.walk():
                content_type = part.get_content_type()
                content_disposition = str(part.get("Content-Disposition"))

                if "attachment" not in content_disposition:
                    if content_type == "text/plain" and not text_body:
                        try:
                            payload = part.get_payload(decode=True)
                            charset = part.get_content_charset() or 'utf-8'
                            text_body = payload.decode(charset, errors='ignore')
                        except Exception as e:
                            logger.error(f"解码文本部分时出错: {e}")
                    elif content_type == "text/html" and not html_body:
                        try:
                            payload = part.get_payload(decode=True)
                            charset = part.get_content_charset() or 'utf-8'
                            html_body = payload.decode(charset, errors='ignore')
                        except Exception as e:
                            logger.error(f"解码 HTML 部分时出错: {e}")
        else:
            content_type = msg.get_content_type()
            try:
                payload = msg.get_payload(decode=True)
                charset = msg.get_content_charset() or 'utf-8'
                if content_type == "text/plain":
                    text_body = payload.decode(charset, errors='ignore')
                elif content_type == "text/html":
                    html_body = payload.decode(charset, errors='ignore')
            except Exception as e:
                logger.error(f"解码非多部分邮件正文时出错: {e}")

        return text_body, html_body

    def _decode_header(self, header: str) -> str:
        if not header:
            return ""
        decoded_parts = decode_header(header)
        header_parts = []
        for part, charset in decoded_parts:
            if isinstance(part, bytes):
                if not charset or charset.lower() == 'unknown-8bit':
                    try:
                        header_parts.append(part.decode('utf-8'))
                    except UnicodeDecodeError:
                        try:
                            header_parts.append(part.decode('gb18030'))
                        except UnicodeDecodeError:
                            header_parts.append(part.decode('latin-1', errors='ignore'))
                else:
                    try:
                        header_parts.append(part.decode(charset, errors='ignore'))
                    except (LookupError, UnicodeDecodeError):
                        try:
                            header_parts.append(part.decode('gb18030'))
                        except UnicodeDecodeError:
                            header_parts.append(part.decode('utf-8', errors='replace'))
            else:
                header_parts.append(part)
        return "".join(header_parts)

    def _create_summary(self, text_body, html_body, has_attachments=False, attachment_count=0):
        """
        生成邮件简介
        规则：
        1. 判断是否存在附件，如果存在附件在前面加上（X个附件）
        2. 邮件正文（不包括附件）>过滤Html标签->过滤常见无意义英文符号->截取前300个有效字符
        3. 两者相加
        """
        # 提取正文内容
        content = text_body if text_body else ""
        
        if html_body:
            soup = BeautifulSoup(html_body, "html.parser")
            for script_or_style in soup(["script", "style"]):
                script_or_style.decompose()
            content = soup.get_text()
        elif text_body and text_body.strip().startswith('<'):
            soup = BeautifulSoup(text_body, "html.parser")
            for script_or_style in soup(["script", "style"]):
                script_or_style.decompose()
            content = soup.get_text()

        # 过滤HTML标签（防御性处理）
        content = re.sub(r'<[^>]+>', '', content)
        content = html.unescape(content)

        # 将URL链接替换为【链接】（支持多种链接格式）
        content = re.sub(r'(?:(?:https?|ftp|sftp)://|www\.)[^\s/$.?#].[^\s]*|\b(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}\S*', '【链接】', content)

        # 过滤常见无意义英文符号
        content = re.sub(r'[.,!?;:\'"()\[\]{}<>/\\_—–-]', ' ', content)
        
        # 移除零宽字符（ZWNJ、ZWJ、零宽空格等）
        content = re.sub(r'[\u200B-\u200D\uFEFF]', '', content)
        
        # 将多个连续的普通空格、制表符、换行符替换为一个空格（不包括零宽字符）
        content = re.sub(r'[ \t\n\r]+', ' ', content).strip()
        
        # 截取前300个有效字符
        body_summary = content[:300]
        
        # 如果有附件，在前面添加附件提示
        if has_attachments and attachment_count > 0:
            attachment_prefix = f"({attachment_count}个附件) "
            return attachment_prefix + body_summary
        
        return body_summary

    def _generate_deterministic_message_id(self, msg, uid):
        subject = self._decode_header(msg.get("Subject", ""))
        sender = self._decode_header(msg.get("From", ""))
        date = msg.get("Date", "")
        
        hash_input = f"{uid}-{subject}-{sender}-{date}".encode('utf-8')
        hash_output = hashlib.sha256(hash_input).hexdigest()
        
        return f"<{hash_output}@{self.account.email.split('@')[-1]}>"

    def _parse_email_metadata(self, msg: email.message.Message, uid: str, folder: str = "", uidvalidity: str = "", db: Session = None):
        # 解析邮件基本信息
        original_message_id = msg.get("Message-ID")
        sender = self._decode_header(msg.get("From", ""))
        subject = self._decode_header(msg.get("Subject", ""))
        recipients = self._decode_header(msg.get("To", ""))
        cc = self._decode_header(msg.get("Cc", ""))
        
        date_str = msg.get("Date", "")
        try:
            from datetime import timezone, timedelta
            
            received_date = email.utils.parsedate_to_datetime(date_str)
            
            # 定义北京时区（UTC+8）
            beijing_tz = timezone(timedelta(hours=8), name='Asia/Shanghai')
            
            # 统一转换为北京时间
            if received_date.tzinfo is not None:
                # 已带时区：智能转换到北京时间（不会重复加时间）
                received_date = received_date.astimezone(beijing_tz)
            else:
                # 不带时区：假定为UTC，然后转换到北京时间
                received_date = received_date.replace(tzinfo=timezone.utc).astimezone(beijing_tz)
                
        except Exception:
            # Fallback：使用当前北京时间
            from api.utils.helpers import get_beijing_time
            received_date = get_beijing_time()
            date_str = received_date.isoformat()

        # 使用新的三层唯一性保障方案
        message_id, is_generated, content_hash = ensure_message_id(
            message_id=original_message_id,
            sender_email=sender,
            recipients=recipients,
            subject=subject,
            date_str=date_str,
            folder=folder,
            uid=uid,
            db=db,
            email_account_id=self.account.id
        )

        # 生成IMAP位置key
        imap_key = generate_imap_key(folder, uid, uidvalidity) if folder and uidvalidity else ""

        # 正确判断是否有附件及附件数量
        has_attachments, attachment_count = self._check_has_attachments(msg)
        
        return {
            "email_account_id": self.account.id,
            "uid": uid,
            "message_id": message_id,
            "is_generated_message_id": is_generated,
            "email_hash": content_hash,  # 保持向后兼容
            "content_hash": content_hash,  # 新字段
            "imap_key": imap_key,  # 新字段
            "subject": subject,
            "sender": sender,
            "recipients": recipients,
            "cc": cc,
            "received_date": received_date,
            "has_attachments": has_attachments,
            "attachment_count": attachment_count,
            "summary": ""
        }

    def _check_has_attachments(self, msg: email.message.Message):
        """
        检查邮件是否包含真正的附件（不包括内嵌图片）
        返回：(has_attachments: bool, attachment_count: int)
        """
        if not msg.is_multipart():
            return False, 0
        
        attachment_count = 0
        for part in msg.walk():
            content_type = part.get_content_type()
            content_disposition = str(part.get("Content-Disposition"))
            content_id = part.get("Content-ID")
            
            # 跳过纯文本和HTML正文部分
            if content_type in ["text/plain", "text/html", "multipart/mixed", "multipart/alternative", "multipart/related"]:
                continue
            
            # 如果有Content-Disposition且包含attachment，则是附件
            if "attachment" in content_disposition:
                filename = part.get_filename()
                if filename:  # 确保有文件名
                    attachment_count += 1
            # 如果不是内嵌图片（没有Content-ID），且有文件名，可能是附件
            elif not content_id:
                filename = part.get_filename()
                if filename:
                    attachment_count += 1
        
        return attachment_count > 0, attachment_count

    def _save_email_to_db(self, email_data: dict, db: Session, uidvalidity: str) -> Optional[Email]:
        """
        保存邮件到数据库，使用Message-ID或email_hash进行去重
        优先使用Message-ID，如果Message-ID是生成的，则同时检查email_hash
        返回创建或更新的Email对象
        """
        email_account_id = email_data["email_account_id"]
        message_id = email_data["message_id"]
        email_hash = email_data["email_hash"]
        folder = email_data["folder"]
        uid = email_data["uid"]
        is_generated = email_data.get("is_generated_message_id", False)
        
        # 查找已存在的邮件记录
        db_email = None
        
        # 策略1：优先通过Message-ID查找（适用于有原始Message-ID的邮件）
        # 注意：同一Message-ID可能在不同账户中存在（转发、群发等情况）
        if message_id:
            db_email = db.query(Email).filter(
                Email.email_account_id == email_account_id,
                Email.message_id == message_id
            ).first()
        
        # 策略2：如果没找到，通过email_hash查找（适用于无Message-ID或生成的Message-ID）
        if not db_email and email_hash:
            db_email = db.query(Email).filter(
                Email.email_account_id == email_account_id,
                Email.email_hash == email_hash
            ).first()
        
        # 策略3：【v2.0已移除】通过EmailFolder关联表查找
        # 旧的folder+uid+uidvalidity组合已迁移到EmailFolder表，这里不再需要
        
        if db_email:
            # 【v2.0】更新现有邮件记录的基本信息（不再更新folder/uid等已迁移字段）
            db_email.is_read = email_data.get("is_read", db_email.is_read)
            
            # 更新邮件主题、发件人、收件人等基本信息
            db_email.subject = email_data.get("subject", db_email.subject)
            db_email.sender = email_data.get("sender", db_email.sender)
            db_email.recipients = email_data.get("recipients", db_email.recipients)
            db_email.cc = email_data.get("cc", db_email.cc)
            db_email.received_date = email_data.get("received_date", db_email.received_date)
            db_email.summary = email_data.get("summary", db_email.summary)
            
            # 更新附件信息
            if "has_attachments" in email_data:
                new_has_attachments = email_data.get("has_attachments", False)
                if db_email.has_attachments != new_has_attachments:
                    db_email.has_attachments = new_has_attachments
            
            # 更新Message-ID
            if not db_email.message_id and message_id:
                db_email.message_id = message_id
                db_email.is_generated_message_id = is_generated
            
            # 更新email_hash
            if not db_email.email_hash and email_hash:
                db_email.email_hash = email_hash
            return db_email
        else:
            # 创建新邮件记录
            try:
                email_data["uidvalidity"] = uidvalidity
                new_email = Email(**email_data)
                db.add(new_email)
                db.flush()
                return new_email
            except Exception as e:
                # 如果插入失败（通常是唯一性约束），说明邮件已存在
                db.rollback()
                logger.error(f"插入邮件失败且无法找到重复记录 - Message-ID: {message_id}...")
                logger.error(f"UID: {uid}, Folder: {folder}, 错误: {str(e)}")
                return None

    def move_email(self, db: Session, email_id: int, current_folder: str, target_folder: str):
        """
        【v2.0改造】移动邮件到指定文件夹
        注意：虚拟文件夹（如UNREAD、STARRED等）不是真实的IMAP文件夹，不能作为目标文件夹
        
        Args:
            db: 数据库会话
            email_id: 邮件ID
            current_folder: 源文件夹名称（必须是真实文件夹，不能是虚拟文件夹）
            target_folder: 目标文件夹名称
        """
        # 定义虚拟文件夹列表
        virtual_folders = ["UNREAD", "STARRED", "FLAGGED"]
        
        # 【新增】如果源文件夹和目标文件夹相同，直接返回成功（不执行任何操作）
        if current_folder == target_folder:
            return  # 直接返回，不抛出异常
        
        # 检查目标文件夹是否为虚拟文件夹
        if target_folder.upper() in virtual_folders:
            raise HTTPException(
                status_code=400,
                detail=f"无法移动邮件到虚拟文件夹 '{target_folder}'。虚拟文件夹是通过筛选条件显示的邮件集合，而非真实的服务器文件夹。"
            )
        
        # 检查源文件夹是否为虚拟文件夹
        if current_folder.upper() in virtual_folders:
            raise HTTPException(
                status_code=400,
                detail=f"无法从虚拟文件夹 '{current_folder}' 移动邮件。请从邮件实际所在的文件夹进行移动操作。"
            )
        
        try:
            # 获取邮件记录
            email_record = db.query(Email).filter(Email.id == email_id).first()
            if not email_record:
                raise HTTPException(status_code=404, detail="未找到邮件")

            # 使用传入的current_folder作为实际源文件夹
            actual_current_folder = current_folder

            self._connect()
            
            # 获取源文件夹和目标文件夹在服务器上的真实名称（已编码）
            original_current_folder = self._get_original_folder_name(actual_current_folder)
            original_target_folder = self._get_original_folder_name(target_folder)
            
            # 验证目标文件夹是否存在
            if original_target_folder == target_folder:
                # 检查文件夹是否真的存在
                # 注意：某些IMAP服务器要求list()必须提供参数
                try:
                    status, folder_list = self.imap.list('""', '*')
                except Exception as list_err:
                    # 如果带参数失败，尝试不带参数
                    status, folder_list = self.imap.list()
                
                folder_exists = False
                if status == 'OK':
                    for folder_data in folder_list:
                        line_str = folder_data.decode('utf-8', 'ignore')
                        # 支持两种格式：name有引号和无引号
                        match = re.match(r'\((.*?)\)\s+"(.*?)"\s+"?(.*?)"?$', line_str)
                        if match:
                            _, _, folder_name = match.groups()
                            folder_name = folder_name.strip('"')  # 移除可能的引号
                            decoded_name = decode_modified_utf7(folder_name)
                            if decoded_name == target_folder:
                                folder_exists = True
                                original_target_folder = folder_name
                                break
                
                # 如果文件夹不存在，直接返回错误
                if not folder_exists:
                    raise HTTPException(
                        status_code=404, 
                        detail=f"目标文件夹 '{target_folder}' 在邮件服务器上不存在，请刷新文件夹列表后重试"
                    )
            
            # 选择源文件夹（使用服务器上的真实名称，带重试）
            status, select_response = self._imap_operation_with_retry(
                lambda: self.imap.select(f'"{original_current_folder}"', readonly=False),
                operation_name=f"选择源文件夹 {actual_current_folder}"
            )
            if status != 'OK':
                raise HTTPException(
                    status_code=500,
                    detail=f"无法选择源文件夹 '{actual_current_folder}': {select_response}"
                )

            # 【v2.0】获取源文件夹的EmailFolder关联
            source_relation = db.query(EmailFolder).filter(
                EmailFolder.email_id == email_id,
                EmailFolder.email_account_id == self.account.id,
                EmailFolder.folder_name == actual_current_folder
            ).first()
            
            if not source_relation:
                raise HTTPException(status_code=404, detail=f"未找到邮件在源文件夹 '{actual_current_folder}' 的关联")
            
            uid_str = str(source_relation.uid)

            # 1. 复制邮件（使用服务器上的真实文件夹名称，带重试）
            result, copy_response = self._imap_operation_with_retry(
                lambda: self.imap.uid('COPY', uid_str, f'"{original_target_folder}"'),
                operation_name=f"复制邮件 UID={uid_str} 到 {target_folder}"
            )
            if result != 'OK':
                error_msg = f"无法复制邮件到 '{target_folder}': {copy_response}"
                logger.error(f"错误: {error_msg}")
                raise HTTPException(status_code=500, detail=error_msg)
            logger.info(f"成功复制邮件到 '{target_folder}'")

            # 2. 在原文件夹中标记为删除（带重试）
            result, store_response = self._imap_operation_with_retry(
                lambda: self.imap.uid('STORE', uid_str, '+FLAGS', '(\\Deleted)'),
                operation_name=f"标记邮件删除 UID={uid_str}"
            )
            if result != 'OK':
                logger.error(f"警告: 标记邮件删除失败: {store_response}")

            # 3. 执行 expunge
            self.imap.expunge()

            # 4. 在目标文件夹中查找新的UID
            new_uid = None
            try:
                # 选择目标文件夹
                status, _ = self.imap.select(f'"{original_target_folder}"', readonly=True)
                if status == 'OK':
                    # 使用Message-ID搜索邮件
                    message_id = email_record.message_id
                    if message_id:
                        # 清理Message-ID（移除可能的<>符号）
                        clean_message_id = message_id.strip('<>')
                        search_query = f'HEADER Message-ID "{clean_message_id}"'
                        status, data = self.imap.uid('search', None, search_query)
                        
                        if status == 'OK' and data and data[0] and data[0] != b'':
                            uids = data[0].split()
                            if uids:
                                new_uid = uids[-1].decode()  # 使用最后一个UID（最新的）
                                logger.info(f"在目标文件夹中找到新UID: {new_uid}")
                            else:
                                pass
                        else:
                            pass
                    
                    # 如果通过Message-ID没找到，尝试通过主题和发件人搜索
                    if not new_uid:
                        status, all_uids_data = self.imap.uid('search', None, "ALL")
                        if status == 'OK' and all_uids_data[0]:
                            all_uids = all_uids_data[0].split()
                            # 只检查最近的几封邮件（刚复制的应该在最后）
                            recent_uids = all_uids[-10:] if len(all_uids) > 10 else all_uids
                            
                            for check_uid in reversed(recent_uids):
                                status, msg_data = self.imap.uid('fetch', check_uid, '(BODY.PEEK[HEADER.FIELDS (SUBJECT FROM)])')
                                if status == 'OK' and msg_data[0]:
                                    header_data = msg_data[0][1]
                                    msg = email.message_from_bytes(header_data)
                                    fetched_subject = self._decode_header(msg.get("Subject", ""))
                                    fetched_sender = self._decode_header(msg.get("From", ""))
                                    
                                    if fetched_subject == email_record.subject and fetched_sender == email_record.sender:
                                        new_uid = check_uid.decode()
                                        break
            except Exception as e:
                logger.error(f"获取新UID失败: {e}")

            # 5. 【v2.0】更新数据库：删除源文件夹关联，创建目标文件夹关联
            # 删除源文件夹关联
            db.delete(source_relation)
            
            # 如果找到了新UID，创建目标文件夹关联
            if new_uid:
                # 获取目标文件夹的UIDVALIDITY
                try:
                    status, select_data = self.imap.select(f'"{original_target_folder}"', readonly=True)
                    if status == 'OK':
                        from api.service.email_sync_helper import get_uidvalidity
                        target_uidvalidity = get_uidvalidity(
                            self.imap, select_data, original_target_folder, target_folder, self.account.id
                        )
                        
                        # 创建新的文件夹关联
                        from api.utils.helpers import generate_imap_key
                        new_imap_key = generate_imap_key(target_folder, new_uid, target_uidvalidity)
                        
                        new_relation = EmailFolder(
                            email_id=email_id,
                            email_account_id=self.account.id,
                            folder_name=target_folder,
                            uid=new_uid,
                            uidvalidity=target_uidvalidity,
                            imap_key=new_imap_key
                        )
                        db.add(new_relation)
                except Exception as e:
                    logger.error(f"创建目标文件夹关联失败: {e}")
            else:
                pass
            
            db.commit()

        except HTTPException:
            db.rollback()
            raise
        except Exception as e:
            db.rollback()
            error_msg = f"移动邮件时发生错误: {str(e)}"
            logger.error(error_msg)
            raise HTTPException(status_code=500, detail=error_msg)
        finally:
            self._disconnect()

    @staticmethod
    def validate_account_config(email: str, password: str, imap_server: str, imap_port: int,
                               imap_ssl: bool, smtp_server: str, smtp_port: int, smtp_ssl: bool,
                               proxy_id: int = None):
        """
        验证邮箱账户配置（IMAP和SMTP），支持代理
        返回: (success: bool, error_message: str)
        """
        # 获取代理配置
        proxy_config = None
        if proxy_id:
            from api.model.proxy import Proxy
            db = SessionLocal()
            try:
                proxy = db.query(Proxy).filter(Proxy.id == proxy_id).first()
                if proxy and proxy.is_active:
                    proxy_config = {
                        'type': proxy.type,
                        'host': proxy.host,
                        'port': proxy.port,
                        'username': proxy.username,
                        'password': proxy.password,
                        'v2ray_config': proxy.v2ray_config  # 添加v2ray配置
                    }
            finally:
                db.close()
        
        # 验证 IMAP
        try:
            if proxy_config:
                imap_server_conn = create_imap_connection_with_proxy(
                    server=imap_server,
                    port=imap_port,
                    use_ssl=imap_ssl,
                    proxy_config=proxy_config,
                    timeout=30
                )
            else:
                imap_server_conn = create_imap_connection(
                    server=imap_server,
                    port=imap_port,
                    use_ssl=imap_ssl,
                    timeout=30
                )
            
            # 登录认证
            imap_server_conn.login(email, password)
            
            # 测试连接后立即断开
            imap_server_conn.logout()
            
        except imaplib.IMAP4.error as e:
            error_msg = str(e)
            logger.error(f"IMAP登录失败: {error_msg}")
            
            if 'LOGIN failed' in error_msg or 'authentication failed' in error_msg.lower():
                suggestion = (
                    "IMAP登录认证失败，可能的原因：\n"
                    "1. 密码错误（注意：很多邮箱需要使用「授权码」而非登录密码）\n"
                    "2. 邮箱未开启IMAP服务（请在邮箱设置中开启）\n"
                    "3. 需要开启「第三方客户端」或「允许不够安全的应用」访问\n"
                    "4. QQ邮箱/163邮箱等需要使用授权码\n\n"
                    f"详细错误: {error_msg}"
                )
                return False, suggestion
            else:
                return False, f"IMAP连接失败: {error_msg}"
                
        except Exception as e:
            logger.error(f"IMAP连接异常: {e}", exc_info=True)
            return False, f"IMAP连接失败: {e}"

        # 验证 SMTP
        try:
            if proxy_config:
                smtp_server_conn = create_smtp_connection_with_proxy(
                    server=smtp_server,
                    port=smtp_port,
                    use_ssl=smtp_ssl,
                    proxy_config=proxy_config,
                    timeout=30
                )
            else:
                smtp_server_conn = create_smtp_connection(
                    server=smtp_server,
                    port=smtp_port,
                    use_ssl=smtp_ssl,
                    timeout=30
                )
            
            # 登录认证
            smtp_server_conn.login(email, password)
            
            # 测试连接后立即断开
            smtp_server_conn.quit()
        except Exception as e:
            return False, f"SMTP 连接失败: {e}"

        return True, "邮箱配置验证成功"

    def get_folders_list(self, force_refresh: bool = False):
        """
        获取邮箱账户的文件夹列表，包括自定义文件夹
        返回: (final_folders: list, folder_mapping: dict)
        """
        # 固定文件夹（包含系统草稿箱）
        fixed_folders_top = [
            {"name": "INBOX", "display_name": "收件箱"},
            {"name": "UNREAD", "display_name": "未读邮件"},
        ]
        
        fixed_folders_bottom = [
            {"name": "Drafts", "display_name": "草稿箱"},
            {"name": "Sent", "display_name": "已发送"},
            {"name": "Junk", "display_name": "垃圾邮件"},
            {"name": "Trash", "display_name": "已删除"},
        ]
        
        # 用于过滤的固定文件夹名称集合
        fixed_folder_names = {"INBOX", "UNREAD", "SENT", "JUNK", "TRASH", "草稿", "DRAFTS", "DRAFT"}

        custom_folders = []
        folder_mapping = {}
        
        try:
            self._connect()
            
            try:
                status, folders_data = self.imap.list('""', '*')
            except:
                status, folders_data = self.imap.list()
            if status == 'OK':
                all_folders = []
                for folder_line in folders_data:
                    line_str = folder_line.decode('utf-8', 'ignore')
                    # 支持两种格式：name有引号和无引号
                    match = re.match(r'\((.*?)\)\s+"(.*?)"\s+"?(.*?)"?$', line_str)
                    if match:
                        flags, delimiter, name = match.groups()
                        name = name.strip('"')  # 移除可能的引号
                        if "\\Noselect" not in flags:
                            decoded_name = decode_modified_utf7(name)
                            
                            # 添加特殊文件夹名称映射
                            if r'\Sent' in flags:
                                decoded_name = "Sent"
                                folder_mapping["Sent"] = {
                                    "display_name": "已发送",
                                    "encoded_name": name,
                                    "flags": flags
                                }
                            elif r'\Junk' in flags:
                                decoded_name = "Junk"
                                folder_mapping["Junk"] = {
                                    "display_name": "垃圾邮件",
                                    "encoded_name": name,
                                    "flags": flags
                                }
                            elif r'\Trash' in flags:
                                decoded_name = "Trash"
                                folder_mapping["Trash"] = {
                                    "display_name": "已删除",
                                    "encoded_name": name,
                                    "flags": flags
                                }
                            # 基于文件夹名称的映射（兼容更多邮件服务商）
                            elif decoded_name in ["Sent Messages", "Sent Items", "已发送邮件", "已发送"]:
                                decoded_name = "Sent"
                                folder_mapping["Sent"] = {
                                    "display_name": "已发送",
                                    "encoded_name": name,
                                    "flags": flags
                                }
                            elif decoded_name in ["Deleted Messages", "Deleted Items", "已删除邮件", "已删除"]:
                                decoded_name = "Trash"
                                folder_mapping["Trash"] = {
                                    "display_name": "已删除",
                                    "encoded_name": name,
                                    "flags": flags
                                }
                            
                            # 剥离命名空间文件夹
                            stripped_name = strip_namespace_folder(decoded_name, self.account.email)
                            
                            # 【修复】跳过虚拟文件夹本身
                            from api.utils.folder_helper import get_provider_by_email
                            provider = get_provider_by_email(self.account.email)
                            if provider:
                                namespace_folders = provider.get('namespace_folders', [])
                                # 如果剥离前后相同，说明这就是命名空间文件夹本身，应该跳过
                                if decoded_name in namespace_folders and stripped_name == decoded_name:
                                    continue
                            
                            # 添加到映射表（使用剥离后的名称作为key）
                            if stripped_name not in folder_mapping:
                                folder_mapping[stripped_name] = {
                                    "display_name": stripped_name,
                                    "encoded_name": name,
                                    "flags": flags
                                }
                            
                            all_folders.append({'name': name, 'decoded_name': stripped_name, 'delimiter': delimiter, 'flags': flags})

                # 过滤自定义文件夹
                custom_folder_list = []
                
                for folder in sorted(all_folders, key=lambda x: x['decoded_name']):
                    folder_name_upper = folder['decoded_name'].upper()
                    
                    if folder_name_upper in fixed_folder_names:
                        continue
                    
                    if "草稿" in folder['decoded_name'] or "DRAFT" in folder_name_upper:
                        continue
                        
                    custom_folder_list.append(folder)
                
                # 构建自定义文件夹的层级结构
                root_folders = {}
                for folder in custom_folder_list:
                    parts = folder['decoded_name'].split(folder['delimiter'])
                    current_level = root_folders

                    for i, part in enumerate(parts):
                        if part not in current_level:
                            current_level[part] = {
                                "name": folder['decoded_name'],
                                "display_name": part,
                                "children": {}
                            }
                        current_level = current_level[part]["children"]
                
                # 转换成列表格式
                def build_list(tree):
                    result = []
                    for key, value in tree.items():
                        item = {
                            "name": value["name"],
                            "display_name": value["display_name"]
                        }
                        if value["children"]:
                            item["children"] = build_list(value["children"])
                        result.append(item)
                    return result

                custom_folders = build_list(root_folders)

            self._disconnect()

        except Exception as e:
            logger.error(f"获取邮箱文件夹失败: {e}")
            # 即使获取失败，也返回固定文件夹列表
            final_folders = fixed_folders_top + fixed_folders_bottom
            return final_folders, folder_mapping
        
        # 组装最终的文件夹列表
        final_folders = fixed_folders_top.copy()
        final_folders.extend(custom_folders)
        final_folders.extend(fixed_folders_bottom)
        
        return final_folders, folder_mapping

    def send_email(self, to: str, subject: str, body: str, cc: str = None, 
                   attachments: list = None, inline_images: list = None):
        """
        发送邮件
        
        Args:
            to: 收件人（逗号分隔）
            subject: 主题
            body: 邮件正文（HTML格式）
            cc: 抄送（逗号分隔，可选）
            attachments: 附件列表 [{'path': 文件路径, 'filename': 文件名}, ...]
            inline_images: 内嵌图片列表 [{'path': 文件路径, 'cid': content-id, 'filename': 文件名}, ...]
            
        Returns:
            (success: bool, error_message: str)
        """
        from email.mime.text import MIMEText
        from email.mime.multipart import MIMEMultipart
        from email.mime.base import MIMEBase
        from email.mime.image import MIMEImage
        from email.header import Header
        from email.utils import formatdate, make_msgid
        from email import encoders
        import mimetypes
        
        try:
            # 创建邮件
            if inline_images or attachments:
                msg = MIMEMultipart('related')
                msg['From'] = Header(f"{self.account.name} <{self.account.email}>", 'utf-8')
                msg['To'] = Header(to, 'utf-8')
                msg['Subject'] = Header(subject, 'utf-8')
                msg['Date'] = formatdate(localtime=True)
                msg['Message-ID'] = make_msgid()
                
                recipients_list = [to]
                if cc:
                    msg['Cc'] = Header(cc, 'utf-8')
                    recipients_list.extend(cc.split(','))
                
                # 创建 multipart/alternative 用于HTML正文
                msg_alternative = MIMEMultipart('alternative')
                msg.attach(msg_alternative)
                
                # 添加邮件正文
                msg_alternative.attach(MIMEText(body, 'html', 'utf-8'))
                
                # 添加内嵌图片
                if inline_images:
                    for img_info in inline_images:
                        try:
                            with open(img_info['path'], 'rb') as f:
                                img_data = f.read()
                            
                            mime_type = mimetypes.guess_type(img_info['filename'])[0]
                            if mime_type and mime_type.startswith('image/'):
                                maintype, subtype = mime_type.split('/', 1)
                                img = MIMEImage(img_data, _subtype=subtype)
                            else:
                                img = MIMEImage(img_data)
                            
                            img.add_header('Content-ID', f'<{img_info["cid"]}>')
                            img.add_header('Content-Disposition', 'inline', filename=img_info['filename'])
                            msg.attach(img)
                        except Exception as e:
                            logger.error(f"添加内嵌图片失败 {img_info['path']}: {e}")
                
                # 添加附件
                if attachments:
                    for att_info in attachments:
                        try:
                            if not os.path.exists(att_info['path']):
                                logger.error(f"附件文件不存在: {att_info['path']}")
                                return False, f"附件不存在: {att_info['filename']}"
                            
                            with open(att_info['path'], 'rb') as f:
                                content = f.read()
                            
                            mime_type = mimetypes.guess_type(att_info['filename'])[0] or 'application/octet-stream'
                            maintype, subtype = mime_type.split('/', 1)
                            
                            part = MIMEBase(maintype, subtype)
                            part.set_payload(content)
                            encoders.encode_base64(part)
                            part.add_header('Content-Disposition', 'attachment', filename=att_info['filename'])
                            
                            msg.attach(part)
                        except Exception as e:
                            logger.error(f"✗ 添加附件失败 {att_info['path']}: {e}", exc_info=True)
                            return False, f"添加附件失败: {att_info['filename']}"
            else:
                # 无附件，使用简单的MIMEText
                msg = MIMEText(body, 'html', 'utf-8')
                msg['From'] = Header(f"{self.account.name} <{self.account.email}>", 'utf-8')
                msg['To'] = Header(to, 'utf-8')
                msg['Subject'] = Header(subject, 'utf-8')
                msg['Date'] = formatdate(localtime=True)
                msg['Message-ID'] = make_msgid()
                
                recipients_list = [to]
                if cc:
                    msg['Cc'] = Header(cc, 'utf-8')
                    recipients_list.extend(cc.split(','))

            # 获取代理配置
            proxy_config = None
            proxy_id = None
            if self.account.proxy_id:
                from api.model.proxy import Proxy
                db = SessionLocal()
                try:
                    proxy = db.query(Proxy).filter(Proxy.id == self.account.proxy_id).first()
                    if proxy and proxy.is_active:
                        proxy_id = proxy.id
                        proxy_config = {
                            'type': proxy.type,
                            'host': proxy.host,
                            'port': proxy.port,
                            'username': proxy.username,
                            'password': proxy.password,
                            'v2ray_config': proxy.v2ray_config  # 添加v2ray配置
                        }
                finally:
                    db.close()
            
            # 直接创建SMTP连接
            # 对于代理连接，使用更长的超时时间（60秒）
            timeout = 60 if proxy_config else 30
            
            if proxy_config:
                smtp_server = create_smtp_connection_with_proxy(
                    server=self.account.smtp_server,
                    port=self.account.smtp_port,
                    use_ssl=self.account.smtp_ssl,
                    proxy_config=proxy_config,
                    timeout=timeout
                )
            else:
                smtp_server = create_smtp_connection(
                    server=self.account.smtp_server,
                    port=self.account.smtp_port,
                    use_ssl=self.account.smtp_ssl,
                    timeout=timeout
                )
            
            # 登录认证
            smtp_server.login(self.account.email, self.account.password)
            
            smtp_server.sendmail(self.account.email, recipients_list, msg.as_string())
            
            logger.info(f"邮件发送成功 - 主题: {subject[:50]}{'...' if len(subject) > 50 else ''}")
            return True, "邮件发送成功"
            
        except Exception as e:
            logger.error(f"邮件发送失败: {e}", exc_info=True)
            return False, f"邮件发送失败: {e}"

    def permanent_delete_emails(self, db: Session, email_ids: list[int]):
        """
        【v2.0改造】彻底删除邮件（从IMAP服务器和数据库）
        """
        if not email_ids:
            return True, "没有要删除的邮件"

        try:
            emails_to_delete = db.query(Email).filter(Email.id.in_(email_ids)).all()
            if not emails_to_delete:
                return True, "未找到要删除的邮件"

            self._connect()
            
            from collections import defaultdict
            relations_by_folder = defaultdict(list)
            
            for email_record in emails_to_delete:
                relations = db.query(EmailFolder).filter(
                    EmailFolder.email_id == email_record.id,
                    EmailFolder.email_account_id == self.account.id
                ).all()
                for rel in relations:
                    relations_by_folder[rel.folder_name].append((email_record, rel))

            all_deleted_successfully = True
            
            for folder_name, items in relations_by_folder.items():
                original_folder_name = self._get_original_folder_name(folder_name)
                status, _ = self.imap.select(f'"{original_folder_name}"', readonly=False)
                if status != 'OK':
                    logger.error(f"无法选择文件夹 '{folder_name}'，跳过删除")
                    all_deleted_successfully = False
                    continue

                uids_to_delete = [str(rel.uid) for _, rel in items]
                uid_set_str = ','.join(uids_to_delete)
                
                if uid_set_str:
                    store_status, _ = self.imap.uid('STORE', uid_set_str, '+FLAGS', '(\\Deleted)')
                    if store_status != 'OK':
                        logger.error(f"在 '{folder_name}' 中标记邮件为已删除失败")
                        all_deleted_successfully = False
                
                self.imap.expunge()

            # 从数据库中删除
            for email_record in emails_to_delete:
                # 删除翻译缓存
                if email_record.eml_path:
                    translation_service.delete_translation_cache_for_email(email_record.eml_path)
                
                # 删除 EML 文件
                if email_record.eml_path and os.path.exists(email_record.eml_path):
                    try:
                        os.remove(email_record.eml_path)
                    except OSError as e:
                        logger.error(f"删除 EML 文件失败 {email_record.eml_path}: {e}")
                
                db.delete(email_record)

            db.commit()
            
            msg = f"成功彻底删除了 {len(emails_to_delete)} 封邮件"
            if not all_deleted_successfully:
                msg += "，但部分邮件可能在服务器上删除失败"
            
            return True, msg

        except Exception as e:
            db.rollback()
            logger.error(f"彻底删除邮件失败: {e}", exc_info=True)
            return False, f"彻底删除邮件失败: {str(e)}"
        finally:
            self._disconnect()
